Reusing the remote inline suggestion view during autofill filtering

* This gets rid of the flicker in the sample keyboard for most of
  tiem time, during filtering.
* We introduce a new object InlineFillUi to store the autofill
  inline candidates returned from the autofill provider. Filtering
  is applied internally to ensure a single place managing
  generating the InlineSuggestionsResponse that is sent to the IME.
  This enables us to reuse the backing remote view for filtering.
* The InlineFillUi is stored in the SessionController.
* The other classes within the UI package are marked as package
  private.
* Also implement the reference count to track the right time for
  releasing the remote view.

Test: atest android.autofillservice.cts.inline (sanity test)

Bug: 151467650
Change-Id: I023bd8cf8acf838088fba2d1d97c0e78d563222e
This commit is contained in:
Feng Cao
2020-04-29 18:22:49 -07:00
parent 9a395c21bb
commit 3b4d3caa54
9 changed files with 492 additions and 245 deletions

View File

@@ -17,17 +17,17 @@
package com.android.server.autofill;
import android.annotation.NonNull;
import android.annotation.Nullable;
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.autofill.ui.InlineFillUi;
import com.android.server.inputmethod.InputMethodManagerInternal;
import java.util.Collections;
import java.util.Optional;
import java.util.function.Consumer;
@@ -46,8 +46,12 @@ final class AutofillInlineSessionController {
@NonNull
private final Handler mHandler;
@Nullable
@GuardedBy("mLock")
private AutofillInlineSuggestionsRequestSession mSession;
@Nullable
@GuardedBy("mLock")
private InlineFillUi mInlineFillUi;
AutofillInlineSessionController(InputMethodManagerInternal inputMethodManagerInternal,
int userId, ComponentName componentName, Handler handler, Object lock) {
@@ -72,16 +76,16 @@ final class AutofillInlineSessionController {
// 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.onInlineSuggestionsResponseLocked(
InlineFillUi.emptyUi(mSession.getAutofillIdLocked()));
mSession.destroySessionLocked();
mInlineFillUi = null;
}
// 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();
}
/**
@@ -101,30 +105,63 @@ final class AutofillInlineSessionController {
/**
* Requests the IME to hide the current suggestions, if any. Returns true if the message is sent
* to the IME.
* to the IME. This only hides the UI temporarily. For example if user starts typing/deleting
* characters, new filterText will kick in and may revive the suggestion UI.
*/
@GuardedBy("mLock")
boolean hideInlineSuggestionsUiLocked(@NonNull AutofillId autofillId) {
if (mSession != null) {
return mSession.onInlineSuggestionsResponseLocked(autofillId,
new InlineSuggestionsResponse(Collections.EMPTY_LIST));
return mSession.onInlineSuggestionsResponseLocked(InlineFillUi.emptyUi(autofillId));
}
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.
* Permanently delete the current inline fill UI. Notify the IME to hide the suggestions as
* well.
*/
@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);
boolean deleteInlineFillUiLocked(@NonNull AutofillId autofillId) {
mInlineFillUi = null;
return hideInlineSuggestionsUiLocked(autofillId);
}
/**
* Updates the inline fill UI with the filter text. It'll send updated inline suggestions to
* the IME.
*/
@GuardedBy("mLock")
boolean filterInlineFillUiLocked(@NonNull AutofillId autofillId, @Nullable String filterText) {
if (mInlineFillUi != null && mInlineFillUi.getAutofillId().equals(autofillId)) {
mInlineFillUi.setFilterText(filterText);
return requestImeToShowInlineSuggestionsLocked();
}
return false;
}
/**
* Set the current inline fill UI. It'll request the IME to show the inline suggestions when
* the IME becomes visible and is focused on the {@code autofillId}.
*
* @return false if the suggestions are not sent to IME because there is no session, or if the
* IME callback is not available in the session.
*/
@GuardedBy("mLock")
boolean setInlineFillUiLocked(@NonNull InlineFillUi inlineFillUi) {
mInlineFillUi = inlineFillUi;
return requestImeToShowInlineSuggestionsLocked();
}
/**
* Sends the suggestions from the current inline fill UI to the IME.
*
* @return false if the suggestions are not sent to IME because there is no session, or if the
* IME callback is not available in the session.
*/
@GuardedBy("mLock")
private boolean requestImeToShowInlineSuggestionsLocked() {
if (mSession != null && mInlineFillUi != null) {
return mSession.onInlineSuggestionsResponseLocked(mInlineFillUi);
}
return false;
}

View File

@@ -27,7 +27,6 @@ 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;
@@ -37,7 +36,7 @@ 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.autofill.ui.InlineSuggestionFactory;
import com.android.server.autofill.ui.InlineFillUi;
import com.android.server.inputmethod.InputMethodManagerInternal;
import java.lang.ref.WeakReference;
@@ -106,7 +105,7 @@ final class AutofillInlineSuggestionsRequestSession {
private boolean mImeInputViewStarted;
@GuardedBy("mLock")
@Nullable
private InlineSuggestionsResponse mInlineSuggestionsResponse;
private InlineFillUi mInlineFillUi;
@GuardedBy("mLock")
private boolean mPreviousResponseIsNotEmpty;
@@ -156,18 +155,20 @@ final class AutofillInlineSuggestionsRequestSession {
* @return false if the IME callback is not available.
*/
@GuardedBy("mLock")
boolean onInlineSuggestionsResponseLocked(@NonNull AutofillId autofillId,
@NonNull InlineSuggestionsResponse inlineSuggestionsResponse) {
boolean onInlineSuggestionsResponseLocked(@NonNull InlineFillUi inlineFillUi) {
if (mDestroyed) {
return false;
}
if (sDebug) Log.d(TAG, "onInlineSuggestionsResponseLocked called for:" + autofillId);
if (sDebug) {
Slog.d(TAG,
"onInlineSuggestionsResponseLocked called for:" + inlineFillUi.getAutofillId());
}
if (mImeRequest == null || mResponseCallback == null) {
return false;
}
// TODO(b/151123764): each session should only correspond to one field.
mAutofillId = autofillId;
mInlineSuggestionsResponse = inlineSuggestionsResponse;
mAutofillId = inlineFillUi.getAutofillId();
mInlineFillUi = inlineFillUi;
maybeUpdateResponseToImeLocked();
return true;
}
@@ -191,12 +192,12 @@ final class AutofillInlineSuggestionsRequestSession {
if (mDestroyed) {
return;
}
if (sDebug) Log.d(TAG, "onCreateInlineSuggestionsRequestLocked called: " + mAutofillId);
if (sDebug) Slog.d(TAG, "onCreateInlineSuggestionsRequestLocked called: " + mAutofillId);
mInputMethodManagerInternal.onCreateInlineSuggestionsRequest(mUserId,
new InlineSuggestionsRequestInfo(mComponentName, mAutofillId, mUiExtras),
new InlineSuggestionsRequestCallbackImpl(this));
mTimeoutCallback = () -> {
Log.w(TAG, "Timed out waiting for IME callback InlineSuggestionsRequest.");
Slog.w(TAG, "Timed out waiting for IME callback InlineSuggestionsRequest.");
handleOnReceiveImeRequest(null, null);
};
mHandler.postDelayed(mTimeoutCallback, CREATE_INLINE_SUGGESTIONS_REQUEST_TIMEOUT_MS);
@@ -207,7 +208,7 @@ final class AutofillInlineSuggestionsRequestSession {
*/
@GuardedBy("mLock")
private void maybeUpdateResponseToImeLocked() {
if (sVerbose) Log.v(TAG, "maybeUpdateResponseToImeLocked called");
if (sVerbose) Slog.v(TAG, "maybeUpdateResponseToImeLocked called");
if (mDestroyed || mResponseCallback == null) {
return;
}
@@ -217,18 +218,19 @@ final class AutofillInlineSuggestionsRequestSession {
// 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.
if (sVerbose) Log.v(TAG, "Send empty inline response");
if (sVerbose) Slog.v(TAG, "Send empty inline response");
updateResponseToImeUncheckLocked(new InlineSuggestionsResponse(Collections.EMPTY_LIST));
mPreviousResponseIsNotEmpty = false;
} else if (mImeInputViewStarted && mInlineSuggestionsResponse != null && match(mAutofillId,
} else if (mImeInputViewStarted && mInlineFillUi != null && match(mAutofillId,
mImeCurrentFieldId)) {
// 2. if IME is visible, and response is not null, send the response
boolean isEmptyResponse = mInlineSuggestionsResponse.getInlineSuggestions().isEmpty();
InlineSuggestionsResponse response = mInlineFillUi.getInlineSuggestionsResponse();
boolean isEmptyResponse = response.getInlineSuggestions().isEmpty();
if (isEmptyResponse && !mPreviousResponseIsNotEmpty) {
// No-op if both the previous response and current response are empty.
return;
}
updateResponseToImeUncheckLocked(mInlineSuggestionsResponse);
updateResponseToImeUncheckLocked(response);
mPreviousResponseIsNotEmpty = !isEmptyResponse;
}
}
@@ -241,10 +243,9 @@ final class AutofillInlineSuggestionsRequestSession {
if (mDestroyed) {
return;
}
if (sDebug) Log.d(TAG, "Send inline response: " + response.getInlineSuggestions().size());
if (sDebug) Slog.d(TAG, "Send inline response: " + response.getInlineSuggestions().size());
try {
mResponseCallback.onInlineSuggestionsResponse(mAutofillId,
InlineSuggestionFactory.copy(response));
mResponseCallback.onInlineSuggestionsResponse(mAutofillId, response);
} catch (RemoteException e) {
Slog.e(TAG, "RemoteException sending InlineSuggestionsResponse to IME");
}
@@ -264,7 +265,7 @@ final class AutofillInlineSuggestionsRequestSession {
mImeRequestReceived = true;
if (mTimeoutCallback != null) {
if (sVerbose) Log.v(TAG, "removing timeout callback");
if (sVerbose) Slog.v(TAG, "removing timeout callback");
mHandler.removeCallbacks(mTimeoutCallback);
mTimeoutCallback = null;
}
@@ -335,7 +336,7 @@ final class AutofillInlineSuggestionsRequestSession {
@BinderThread
@Override
public void onInlineSuggestionsUnsupported() throws RemoteException {
if (sDebug) Log.d(TAG, "onInlineSuggestionsUnsupported() called.");
if (sDebug) Slog.d(TAG, "onInlineSuggestionsUnsupported() called.");
final AutofillInlineSuggestionsRequestSession session = mSession.get();
if (session != null) {
session.mHandler.sendMessage(obtainMessage(
@@ -348,7 +349,7 @@ final class AutofillInlineSuggestionsRequestSession {
@Override
public void onInlineSuggestionsRequest(InlineSuggestionsRequest request,
IInlineSuggestionsResponseCallback callback) {
if (sDebug) Log.d(TAG, "onInlineSuggestionsRequest() received: " + request);
if (sDebug) Slog.d(TAG, "onInlineSuggestionsRequest() received: " + request);
final AutofillInlineSuggestionsRequestSession session = mSession.get();
if (session != null) {
session.mHandler.sendMessage(obtainMessage(
@@ -359,7 +360,7 @@ final class AutofillInlineSuggestionsRequestSession {
@Override
public void onInputMethodStartInput(AutofillId imeFieldId) throws RemoteException {
if (sVerbose) Log.v(TAG, "onInputMethodStartInput() received on " + imeFieldId);
if (sVerbose) Slog.v(TAG, "onInputMethodStartInput() received on " + imeFieldId);
final AutofillInlineSuggestionsRequestSession session = mSession.get();
if (session != null) {
session.mHandler.sendMessage(obtainMessage(
@@ -371,14 +372,14 @@ final class AutofillInlineSuggestionsRequestSession {
@Override
public void onInputMethodShowInputRequested(boolean requestResult) throws RemoteException {
if (sVerbose) {
Log.v(TAG, "onInputMethodShowInputRequested() received: " + requestResult);
Slog.v(TAG, "onInputMethodShowInputRequested() received: " + requestResult);
}
}
@BinderThread
@Override
public void onInputMethodStartInputView() {
if (sVerbose) Log.v(TAG, "onInputMethodStartInputView() received");
if (sVerbose) Slog.v(TAG, "onInputMethodStartInputView() received");
final AutofillInlineSuggestionsRequestSession session = mSession.get();
if (session != null) {
session.mHandler.sendMessage(obtainMessage(
@@ -390,7 +391,7 @@ final class AutofillInlineSuggestionsRequestSession {
@BinderThread
@Override
public void onInputMethodFinishInputView() {
if (sVerbose) Log.v(TAG, "onInputMethodFinishInputView() received");
if (sVerbose) Slog.v(TAG, "onInputMethodFinishInputView() received");
final AutofillInlineSuggestionsRequestSession session = mSession.get();
if (session != null) {
session.mHandler.sendMessage(obtainMessage(
@@ -401,7 +402,7 @@ final class AutofillInlineSuggestionsRequestSession {
@Override
public void onInputMethodFinishInput() throws RemoteException {
if (sVerbose) Log.v(TAG, "onInputMethodFinishInput() received");
if (sVerbose) Slog.v(TAG, "onInputMethodFinishInput() received");
final AutofillInlineSuggestionsRequestSession session = mSession.get();
if (session != null) {
session.mHandler.sendMessage(obtainMessage(

View File

@@ -48,17 +48,15 @@ import android.view.autofill.AutofillManager;
import android.view.autofill.AutofillValue;
import android.view.autofill.IAutoFillManagerClient;
import android.view.inputmethod.InlineSuggestionsRequest;
import android.view.inputmethod.InlineSuggestionsResponse;
import com.android.internal.infra.AbstractRemoteService;
import com.android.internal.infra.AndroidFuture;
import com.android.internal.infra.ServiceConnector;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.os.IResultReceiver;
import com.android.server.autofill.ui.InlineSuggestionFactory;
import com.android.server.autofill.ui.InlineFillUi;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CancellationException;
import java.util.concurrent.TimeUnit;
@@ -149,7 +147,7 @@ final class RemoteAugmentedAutofillService
int taskId, @NonNull ComponentName activityComponent, @NonNull AutofillId focusedId,
@Nullable AutofillValue focusedValue,
@Nullable InlineSuggestionsRequest inlineSuggestionsRequest,
@Nullable Function<InlineSuggestionsResponse, Boolean> inlineSuggestionsCallback,
@Nullable Function<InlineFillUi, Boolean> inlineSuggestionsCallback,
@NonNull Runnable onErrorCallback,
@Nullable RemoteInlineSuggestionRenderService remoteRenderService) {
long requestTime = SystemClock.elapsedRealtime();
@@ -173,7 +171,8 @@ final class RemoteAugmentedAutofillService
mCallbacks.resetLastResponse();
maybeRequestShowInlineSuggestions(sessionId,
inlineSuggestionsRequest, inlineSuggestionsData,
clientState, focusedId, inlineSuggestionsCallback,
clientState, focusedId, focusedValue,
inlineSuggestionsCallback,
client, onErrorCallback, remoteRenderService);
requestAutofill.complete(null);
}
@@ -239,8 +238,8 @@ final class RemoteAugmentedAutofillService
private void maybeRequestShowInlineSuggestions(int sessionId,
@Nullable InlineSuggestionsRequest request,
@Nullable List<Dataset> inlineSuggestionsData, @Nullable Bundle clientState,
@NonNull AutofillId focusedId,
@Nullable Function<InlineSuggestionsResponse, Boolean> inlineSuggestionsCallback,
@NonNull AutofillId focusedId, @Nullable AutofillValue focusedValue,
@Nullable Function<InlineFillUi, Boolean> inlineSuggestionsCallback,
@NonNull IAutoFillManagerClient client, @NonNull Runnable onErrorCallback,
@Nullable RemoteInlineSuggestionRenderService remoteRenderService) {
if (inlineSuggestionsData == null || inlineSuggestionsData.isEmpty()
@@ -250,10 +249,14 @@ final class RemoteAugmentedAutofillService
}
mCallbacks.setLastResponse(sessionId);
final InlineSuggestionsResponse inlineSuggestionsResponse =
InlineSuggestionFactory.createAugmentedInlineSuggestionsResponse(
request, inlineSuggestionsData, focusedId,
new InlineSuggestionFactory.InlineSuggestionUiCallback() {
final String filterText =
focusedValue != null && focusedValue.isText()
? focusedValue.getTextValue().toString() : null;
final InlineFillUi inlineFillUi =
InlineFillUi.forAugmentedAutofill(
request, inlineSuggestionsData, focusedId, filterText,
new InlineFillUi.InlineSuggestionUiCallback() {
@Override
public void autofill(Dataset dataset) {
mCallbacks.logAugmentedAutofillSelected(sessionId,
@@ -265,8 +268,8 @@ final class RemoteAugmentedAutofillService
&& fieldIds.get(0).equals(focusedId);
client.autofill(sessionId, fieldIds, dataset.getFieldValues(),
hideHighlight);
inlineSuggestionsCallback.apply(new InlineSuggestionsResponse(
Collections.EMPTY_LIST));
inlineSuggestionsCallback.apply(
InlineFillUi.emptyUi(focusedId));
} catch (RemoteException e) {
Slog.w(TAG, "Encounter exception autofilling the values");
}
@@ -283,11 +286,7 @@ final class RemoteAugmentedAutofillService
}
}, onErrorCallback, remoteRenderService);
if (inlineSuggestionsResponse == null) {
Slog.w(TAG, "InlineSuggestionFactory created null response");
return;
}
if (inlineSuggestionsCallback.apply(inlineSuggestionsResponse)) {
if (inlineSuggestionsCallback.apply(inlineFillUi)) {
mCallbacks.logAugmentedAutofillShown(sessionId, clientState);
}
}

View File

@@ -94,7 +94,6 @@ import android.view.autofill.AutofillValue;
import android.view.autofill.IAutoFillManagerClient;
import android.view.autofill.IAutofillWindowPresenter;
import android.view.inputmethod.InlineSuggestionsRequest;
import android.view.inputmethod.InlineSuggestionsResponse;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
@@ -102,7 +101,7 @@ import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.util.ArrayUtils;
import com.android.server.autofill.ui.AutoFillUI;
import com.android.server.autofill.ui.InlineSuggestionFactory;
import com.android.server.autofill.ui.InlineFillUi;
import com.android.server.autofill.ui.PendingUi;
import com.android.server.inputmethod.InputMethodManagerInternal;
@@ -2662,10 +2661,20 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
}
} else if (viewState.id.equals(this.mCurrentViewId)
&& (viewState.getState() & ViewState.STATE_INLINE_SHOWN) != 0) {
requestShowInlineSuggestionsLocked(viewState.getResponse(), filterText);
if ((viewState.getState() & ViewState.STATE_INLINE_DISABLED) != 0) {
final FillResponse response = viewState.getResponse();
if (response != null) {
response.getDatasets().clear();
}
mInlineSessionController.deleteInlineFillUiLocked(viewState.id);
} else {
mInlineSessionController.filterInlineFillUiLocked(mCurrentViewId, filterText);
}
} else if (viewState.id.equals(this.mCurrentViewId)
&& (viewState.getState() & ViewState.STATE_TRIGGERED_AUGMENTED_AUTOFILL) != 0) {
if (!TextUtils.isEmpty(filterText)) {
// TODO: we should be able to replace this with controller#filterInlineFillUiLocked
// to accomplish filtering for augmented autofill.
mInlineSessionController.hideInlineSuggestionsUiLocked(mCurrentViewId);
}
}
@@ -2816,26 +2825,15 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
return false;
}
final ViewState currentView = mViewStates.get(focusedId);
if ((currentView.getState() & ViewState.STATE_INLINE_DISABLED) != 0) {
response.getDatasets().clear();
}
InlineSuggestionsResponse inlineSuggestionsResponse =
InlineSuggestionFactory.createInlineSuggestionsResponse(
inlineSuggestionsRequest.get(), response, filterText, focusedId,
this, () -> {
synchronized (mLock) {
mInlineSessionController.hideInlineSuggestionsUiLocked(
focusedId);
}
}, remoteRenderService);
if (inlineSuggestionsResponse == null) {
Slog.w(TAG, "InlineSuggestionFactory created null response");
return false;
}
return mInlineSessionController.onInlineSuggestionsResponseLocked(focusedId,
inlineSuggestionsResponse);
InlineFillUi inlineFillUi = InlineFillUi.forAutofill(
inlineSuggestionsRequest.get(), response, focusedId, filterText,
/*uiCallback*/this, /*onErrorCallback*/ () -> {
synchronized (mLock) {
mInlineSessionController.hideInlineSuggestionsUiLocked(
focusedId);
}
}, remoteRenderService);
return mInlineSessionController.setInlineFillUiLocked(inlineFillUi);
}
boolean isDestroyed() {
@@ -3119,11 +3117,10 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
final AutofillId focusedId = mCurrentViewId;
final Function<InlineSuggestionsResponse, Boolean> inlineSuggestionsResponseCallback =
final Function<InlineFillUi, Boolean> inlineSuggestionsResponseCallback =
response -> {
synchronized (mLock) {
return mInlineSessionController.onInlineSuggestionsResponseLocked(
focusedId, response);
return mInlineSessionController.setInlineFillUiLocked(response);
}
};
final Consumer<InlineSuggestionsRequest> requestAugmentedAutofill =

View File

@@ -130,9 +130,9 @@ final class FillUi {
}
FillUi(@NonNull Context context, @NonNull FillResponse response,
@NonNull AutofillId focusedViewId, @NonNull @Nullable String filterText,
@NonNull OverlayControl overlayControl, @NonNull CharSequence serviceLabel,
@NonNull Drawable serviceIcon, boolean nightMode, @NonNull Callback callback) {
@NonNull AutofillId focusedViewId, @Nullable String filterText,
@NonNull OverlayControl overlayControl, @NonNull CharSequence serviceLabel,
@NonNull Drawable serviceIcon, boolean nightMode, @NonNull Callback callback) {
if (sVerbose) Slog.v(TAG, "nightMode: " + nightMode);
mThemeId = nightMode ? THEME_ID_DARK : THEME_ID_LIGHT;
mCallback = callback;

View File

@@ -47,7 +47,7 @@ import com.android.server.FgThread;
*
* See also {@link RemoteInlineSuggestionUi} for relevant information.
*/
public final class InlineContentProviderImpl extends IInlineContentProvider.Stub {
final class InlineContentProviderImpl extends IInlineContentProvider.Stub {
// TODO(b/153615023): consider not holding strong reference to heavy objects in this stub, to
// avoid memory leak in case the client app is holding the remote reference for a longer

View File

@@ -0,0 +1,282 @@
/*
* 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.ui;
import static com.android.server.autofill.Helper.sVerbose;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Intent;
import android.content.IntentSender;
import android.service.autofill.Dataset;
import android.service.autofill.FillResponse;
import android.service.autofill.InlinePresentation;
import android.text.TextUtils;
import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
import android.view.autofill.AutofillId;
import android.view.autofill.AutofillValue;
import android.view.inputmethod.InlineSuggestion;
import android.view.inputmethod.InlineSuggestionsRequest;
import android.view.inputmethod.InlineSuggestionsResponse;
import com.android.internal.view.inline.IInlineContentProvider;
import com.android.server.autofill.RemoteInlineSuggestionRenderService;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.regex.Pattern;
/**
* UI for a particular field (i.e. {@link AutofillId}) based on an inline autofill response from
* the autofill service or the augmented autofill service. It wraps multiple inline suggestions.
*
* <p> This class is responsible for filtering the suggestions based on the filtered text.
* It'll create {@link InlineSuggestion} instances by reusing the backing remote views (from the
* renderer service) if possible.
*/
public final class InlineFillUi {
private static final String TAG = "InlineFillUi";
/**
* The id of the field which the current Ui is for.
*/
@NonNull
final AutofillId mAutofillId;
/**
* The list of inline suggestions, before applying any filtering
*/
@NonNull
private final ArrayList<InlineSuggestion> mInlineSuggestions;
/**
* The corresponding data sets for the inline suggestions. The list may be null if the current
* Ui is the authentication UI for the response. If non-null, the size of data sets should equal
* that of inline suggestions.
*/
@Nullable
private final ArrayList<Dataset> mDatasets;
/**
* The filter text which will be applied on the inline suggestion list before they are returned
* as a response.
*/
@Nullable
private String mFilterText;
/**
* Returns an empty inline autofill UI.
*/
@NonNull
public static InlineFillUi emptyUi(@NonNull AutofillId autofillId) {
return new InlineFillUi(autofillId, new SparseArray<>(), null);
}
/**
* Returns an inline autofill UI for a field based on an Autofilll response.
*/
@NonNull
public static InlineFillUi forAutofill(@NonNull InlineSuggestionsRequest request,
@NonNull FillResponse response,
@NonNull AutofillId focusedViewId, @Nullable String filterText,
@NonNull AutoFillUI.AutoFillUiCallback uiCallback,
@NonNull Runnable onErrorCallback,
@Nullable RemoteInlineSuggestionRenderService remoteRenderService) {
if (InlineSuggestionFactory.responseNeedAuthentication(response)) {
InlineSuggestion inlineAuthentication =
InlineSuggestionFactory.createInlineAuthentication(request, response,
focusedViewId, uiCallback, onErrorCallback, remoteRenderService);
return new InlineFillUi(focusedViewId, inlineAuthentication, filterText);
} else if (response.getDatasets() != null) {
SparseArray<Pair<Dataset, InlineSuggestion>> inlineSuggestions =
InlineSuggestionFactory.createAutofillInlineSuggestions(request,
response.getRequestId(),
response.getDatasets(), focusedViewId, uiCallback, onErrorCallback,
remoteRenderService);
return new InlineFillUi(focusedViewId, inlineSuggestions, filterText);
}
return new InlineFillUi(focusedViewId, new SparseArray<>(), filterText);
}
/**
* Returns an inline autofill UI for a field based on an Autofilll response.
*/
@NonNull
public static InlineFillUi forAugmentedAutofill(@NonNull InlineSuggestionsRequest request,
@NonNull List<Dataset> datasets,
@NonNull AutofillId focusedViewId, @Nullable String filterText,
@NonNull InlineSuggestionUiCallback uiCallback,
@NonNull Runnable onErrorCallback,
@Nullable RemoteInlineSuggestionRenderService remoteRenderService) {
SparseArray<Pair<Dataset, InlineSuggestion>> inlineSuggestions =
InlineSuggestionFactory.createAugmentedAutofillInlineSuggestions(request, datasets,
focusedViewId,
uiCallback, onErrorCallback, remoteRenderService);
return new InlineFillUi(focusedViewId, inlineSuggestions, filterText);
}
InlineFillUi(@NonNull AutofillId autofillId,
@NonNull SparseArray<Pair<Dataset, InlineSuggestion>> inlineSuggestions,
@Nullable String filterText) {
mAutofillId = autofillId;
int size = inlineSuggestions.size();
mDatasets = new ArrayList<>(size);
mInlineSuggestions = new ArrayList<>(size);
for (int i = 0; i < size; i++) {
Pair<Dataset, InlineSuggestion> value = inlineSuggestions.valueAt(i);
mDatasets.add(value.first);
mInlineSuggestions.add(value.second);
}
mFilterText = filterText;
}
InlineFillUi(@NonNull AutofillId autofillId, InlineSuggestion inlineSuggestion,
@Nullable String filterText) {
mAutofillId = autofillId;
mDatasets = null;
mInlineSuggestions = new ArrayList<>();
mInlineSuggestions.add(inlineSuggestion);
mFilterText = filterText;
}
@NonNull
public AutofillId getAutofillId() {
return mAutofillId;
}
public void setFilterText(@Nullable String filterText) {
mFilterText = filterText;
}
/**
* Returns the list of filtered inline suggestions suitable for being sent to the IME.
*/
@NonNull
public InlineSuggestionsResponse getInlineSuggestionsResponse() {
final int size = mInlineSuggestions.size();
if (size == 0) {
return new InlineSuggestionsResponse(Collections.emptyList());
}
final List<InlineSuggestion> inlineSuggestions = new ArrayList<>();
if (mDatasets == null || mDatasets.size() != size) {
// authentication case
for (int i = 0; i < size; i++) {
inlineSuggestions.add(copy(i, mInlineSuggestions.get(i)));
}
return new InlineSuggestionsResponse(inlineSuggestions);
}
for (int i = 0; i < size; i++) {
final Dataset dataset = mDatasets.get(i);
final int fieldIndex = dataset.getFieldIds().indexOf(mAutofillId);
if (fieldIndex < 0) {
Slog.w(TAG, "AutofillId=" + mAutofillId + " not found in dataset");
continue;
}
final InlinePresentation inlinePresentation = dataset.getFieldInlinePresentation(
fieldIndex);
if (inlinePresentation == null) {
Slog.w(TAG, "InlinePresentation not found in dataset");
continue;
}
if (!inlinePresentation.isPinned() // don't filter pinned suggestions
&& !includeDataset(dataset, fieldIndex, mFilterText)) {
continue;
}
inlineSuggestions.add(copy(i, mInlineSuggestions.get(i)));
}
return new InlineSuggestionsResponse(inlineSuggestions);
}
/**
* Returns a copy of the suggestion, that internally copies the {@link IInlineContentProvider}
* so that it's not reused by the remote IME process across different inline suggestions.
* See {@link InlineContentProviderImpl} for why this is needed.
*
* <p>Note that although it copies the {@link IInlineContentProvider}, the underlying remote
* view (in the renderer service) is still reused.
*/
@NonNull
private InlineSuggestion copy(int index, @NonNull InlineSuggestion inlineSuggestion) {
final IInlineContentProvider contentProvider = inlineSuggestion.getContentProvider();
if (contentProvider instanceof InlineContentProviderImpl) {
// We have to create a new inline suggestion instance to ensure we don't reuse the
// same {@link IInlineContentProvider}, but the underlying views are reused when
// calling {@link InlineContentProviderImpl#copy()}.
InlineSuggestion newInlineSuggestion = new InlineSuggestion(inlineSuggestion
.getInfo(), ((InlineContentProviderImpl) contentProvider).copy());
// The remote view is only set when the content provider is called to inflate the view,
// which happens after it's sent to the IME (i.e. not now), so we keep the latest
// content provider (through newInlineSuggestion) to make sure the next time we copy it,
// we get to reuse the view.
mInlineSuggestions.set(index, newInlineSuggestion);
return newInlineSuggestion;
}
return inlineSuggestion;
}
// TODO: Extract the shared filtering logic here and in FillUi to a common method.
private static boolean includeDataset(Dataset dataset, int fieldIndex,
@Nullable String filterText) {
// Show everything when the user input is empty.
if (TextUtils.isEmpty(filterText)) {
return true;
}
final String constraintLowerCase = filterText.toString().toLowerCase();
// Use the filter provided by the service, if available.
final Dataset.DatasetFieldFilter filter = dataset.getFilter(fieldIndex);
if (filter != null) {
Pattern filterPattern = filter.pattern;
if (filterPattern == null) {
if (sVerbose) {
Slog.v(TAG, "Explicitly disabling filter for dataset id" + dataset.getId());
}
return true;
}
return filterPattern.matcher(constraintLowerCase).matches();
}
final AutofillValue value = dataset.getFieldValues().get(fieldIndex);
if (value == null || !value.isText()) {
return dataset.getAuthentication() != null;
}
final String valueText = value.getTextValue().toString().toLowerCase();
return valueText.toLowerCase().startsWith(constraintLowerCase);
}
/**
* Callback from the inline suggestion Ui.
*/
public interface InlineSuggestionUiCallback {
/**
* Callback to autofill a dataset to the client app.
*/
void autofill(@NonNull Dataset dataset);
/**
* Callback to start Intent in client app.
*/
void startIntentSender(@NonNull IntentSender intentSender, @NonNull Intent intent);
}
}

View File

@@ -17,7 +17,6 @@
package com.android.server.autofill.ui;
import static com.android.server.autofill.Helper.sDebug;
import static com.android.server.autofill.Helper.sVerbose;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -27,11 +26,11 @@ import android.os.IBinder;
import android.service.autofill.Dataset;
import android.service.autofill.FillResponse;
import android.service.autofill.InlinePresentation;
import android.text.TextUtils;
import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
import android.view.autofill.AutofillId;
import android.view.autofill.AutofillManager;
import android.view.autofill.AutofillValue;
import android.view.inputmethod.InlineSuggestion;
import android.view.inputmethod.InlineSuggestionInfo;
import android.view.inputmethod.InlineSuggestionsRequest;
@@ -41,49 +40,34 @@ import android.widget.inline.InlinePresentationSpec;
import com.android.internal.view.inline.IInlineContentProvider;
import com.android.server.autofill.RemoteInlineSuggestionRenderService;
import java.util.ArrayList;
import java.util.List;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.regex.Pattern;
public final class InlineSuggestionFactory {
final class InlineSuggestionFactory {
private static final String TAG = "InlineSuggestionFactory";
/**
* Callback from the inline suggestion Ui.
*/
public interface InlineSuggestionUiCallback {
/**
* Callback to autofill a dataset to the client app.
*/
void autofill(@NonNull Dataset dataset);
/**
* Callback to start Intent in client app.
*/
void startIntentSender(@NonNull IntentSender intentSender, @NonNull Intent intent);
public static boolean responseNeedAuthentication(@NonNull FillResponse response) {
return response.getAuthentication() != null && response.getInlinePresentation() != null;
}
/**
* Returns a copy of the response, that internally copies the {@link IInlineContentProvider}
* so that it's not reused by the remote IME process across different inline suggestions.
* See {@link InlineContentProviderImpl} for why this is needed.
*/
@NonNull
public static InlineSuggestionsResponse copy(@NonNull InlineSuggestionsResponse response) {
final ArrayList<InlineSuggestion> copiedInlineSuggestions = new ArrayList<>();
for (InlineSuggestion inlineSuggestion : response.getInlineSuggestions()) {
final IInlineContentProvider contentProvider = inlineSuggestion.getContentProvider();
if (contentProvider instanceof InlineContentProviderImpl) {
copiedInlineSuggestions.add(new
InlineSuggestion(inlineSuggestion.getInfo(),
((InlineContentProviderImpl) contentProvider).copy()));
} else {
copiedInlineSuggestions.add(inlineSuggestion);
}
}
return new InlineSuggestionsResponse(copiedInlineSuggestions);
public static InlineSuggestion createInlineAuthentication(
@NonNull InlineSuggestionsRequest request, @NonNull FillResponse response,
@NonNull AutofillId autofillId,
@NonNull AutoFillUI.AutoFillUiCallback client, @NonNull Runnable onErrorCallback,
@Nullable RemoteInlineSuggestionRenderService remoteRenderService) {
final BiConsumer<Dataset, Integer> onClickFactory = (dataset, datasetIndex) -> {
client.requestHideFillUi(autofillId);
client.authenticate(response.getRequestId(),
datasetIndex, response.getAuthentication(), response.getClientState(),
/* authenticateInline= */ true);
};
final Consumer<IntentSender> intentSenderConsumer = (intentSender) ->
client.startIntentSender(intentSender, new Intent());
InlinePresentation inlineAuthentication = response.getInlinePresentation();
return createInlineAuthSuggestion(inlineAuthentication,
remoteRenderService, onClickFactory, onErrorCallback, intentSenderConsumer,
request.getHostInputToken(), request.getHostDisplayId());
}
/**
@@ -91,33 +75,23 @@ public final class InlineSuggestionFactory {
* autofill service, potentially filtering the datasets.
*/
@Nullable
public static InlineSuggestionsResponse createInlineSuggestionsResponse(
@NonNull InlineSuggestionsRequest request, @NonNull FillResponse response,
@Nullable String filterText, @NonNull AutofillId autofillId,
public static SparseArray<Pair<Dataset, InlineSuggestion>> createAutofillInlineSuggestions(
@NonNull InlineSuggestionsRequest request, int requestId,
@NonNull List<Dataset> datasets,
@NonNull AutofillId autofillId,
@NonNull AutoFillUI.AutoFillUiCallback client, @NonNull Runnable onErrorCallback,
@Nullable RemoteInlineSuggestionRenderService remoteRenderService) {
if (sDebug) Slog.d(TAG, "createInlineSuggestionsResponse called");
final BiConsumer<Dataset, Integer> onClickFactory;
if (response.getAuthentication() != null) {
onClickFactory = (dataset, datasetIndex) -> {
client.requestHideFillUi(autofillId);
client.authenticate(response.getRequestId(),
datasetIndex, response.getAuthentication(), response.getClientState(),
/* authenticateInline= */ true);
};
} else {
onClickFactory = (dataset, datasetIndex) -> {
client.requestHideFillUi(autofillId);
client.fill(response.getRequestId(), datasetIndex, dataset);
};
}
final Consumer<IntentSender> intentSenderConsumer = (intentSender) ->
client.startIntentSender(intentSender, new Intent());
final BiConsumer<Dataset, Integer> onClickFactory = (dataset, datasetIndex) -> {
client.requestHideFillUi(autofillId);
client.fill(requestId, datasetIndex, dataset);
};
final InlinePresentation inlineAuthentication =
response.getAuthentication() == null ? null : response.getInlinePresentation();
return createInlineSuggestionsResponseInternal(/* isAugmented= */ false, request,
response.getDatasets(), filterText, inlineAuthentication, autofillId,
onErrorCallback, onClickFactory, (intentSender) ->
client.startIntentSender(intentSender, new Intent()), remoteRenderService);
return createInlineSuggestionsInternal(/* isAugmented= */ false, request,
datasets, autofillId,
onErrorCallback, onClickFactory, intentSenderConsumer, remoteRenderService);
}
/**
@@ -125,16 +99,16 @@ public final class InlineSuggestionFactory {
* autofill service.
*/
@Nullable
public static InlineSuggestionsResponse createAugmentedInlineSuggestionsResponse(
public static SparseArray<Pair<Dataset, InlineSuggestion>>
createAugmentedAutofillInlineSuggestions(
@NonNull InlineSuggestionsRequest request, @NonNull List<Dataset> datasets,
@NonNull AutofillId autofillId,
@NonNull InlineSuggestionUiCallback inlineSuggestionUiCallback,
@NonNull InlineFillUi.InlineSuggestionUiCallback inlineSuggestionUiCallback,
@NonNull Runnable onErrorCallback,
@Nullable RemoteInlineSuggestionRenderService remoteRenderService) {
if (sDebug) Slog.d(TAG, "createAugmentedInlineSuggestionsResponse called");
return createInlineSuggestionsResponseInternal(/* isAugmented= */ true, request,
datasets, /* filterText= */ null, /* inlineAuthentication= */ null,
autofillId, onErrorCallback,
return createInlineSuggestionsInternal(/* isAugmented= */ true, request,
datasets, autofillId, onErrorCallback,
(dataset, datasetIndex) ->
inlineSuggestionUiCallback.autofill(dataset),
(intentSender) ->
@@ -143,29 +117,13 @@ public final class InlineSuggestionFactory {
}
@Nullable
private static InlineSuggestionsResponse createInlineSuggestionsResponseInternal(
private static SparseArray<Pair<Dataset, InlineSuggestion>> createInlineSuggestionsInternal(
boolean isAugmented, @NonNull InlineSuggestionsRequest request,
@Nullable List<Dataset> datasets, @Nullable String filterText,
@Nullable InlinePresentation inlineAuthentication, @NonNull AutofillId autofillId,
@NonNull List<Dataset> datasets, @NonNull AutofillId autofillId,
@NonNull Runnable onErrorCallback, @NonNull BiConsumer<Dataset, Integer> onClickFactory,
@NonNull Consumer<IntentSender> intentSenderConsumer,
@Nullable RemoteInlineSuggestionRenderService remoteRenderService) {
final ArrayList<InlineSuggestion> inlineSuggestions = new ArrayList<>();
if (inlineAuthentication != null) {
InlineSuggestion inlineAuthSuggestion = createInlineAuthSuggestion(inlineAuthentication,
remoteRenderService, onClickFactory, onErrorCallback, intentSenderConsumer,
request.getHostInputToken(), request.getHostDisplayId());
inlineSuggestions.add(inlineAuthSuggestion);
return new InlineSuggestionsResponse(inlineSuggestions);
}
if (datasets == null) {
Slog.w(TAG, "Datasets should not be null here");
return null;
}
SparseArray<Pair<Dataset, InlineSuggestion>> response = new SparseArray<>(datasets.size());
for (int datasetIndex = 0; datasetIndex < datasets.size(); datasetIndex++) {
final Dataset dataset = datasets.get(datasetIndex);
final int fieldIndex = dataset.getFieldIds().indexOf(autofillId);
@@ -179,50 +137,14 @@ public final class InlineSuggestionFactory {
Slog.w(TAG, "InlinePresentation not found in dataset");
continue;
}
if (!inlinePresentation.isPinned() // don't filter pinned suggestions
&& !includeDataset(dataset, fieldIndex, filterText)) {
continue;
}
InlineSuggestion inlineSuggestion = createInlineSuggestion(isAugmented, dataset,
datasetIndex,
mergedInlinePresentation(request, datasetIndex, inlinePresentation),
onClickFactory, remoteRenderService, onErrorCallback, intentSenderConsumer,
request.getHostInputToken(), request.getHostDisplayId());
inlineSuggestions.add(inlineSuggestion);
response.append(datasetIndex, Pair.create(dataset, inlineSuggestion));
}
return new InlineSuggestionsResponse(inlineSuggestions);
}
// TODO: Extract the shared filtering logic here and in FillUi to a common method.
private static boolean includeDataset(Dataset dataset, int fieldIndex,
@Nullable String filterText) {
// Show everything when the user input is empty.
if (TextUtils.isEmpty(filterText)) {
return true;
}
final String constraintLowerCase = filterText.toString().toLowerCase();
// Use the filter provided by the service, if available.
final Dataset.DatasetFieldFilter filter = dataset.getFilter(fieldIndex);
if (filter != null) {
Pattern filterPattern = filter.pattern;
if (filterPattern == null) {
if (sVerbose) {
Slog.v(TAG, "Explicitly disabling filter for dataset id" + dataset.getId());
}
return true;
}
return filterPattern.matcher(constraintLowerCase).matches();
}
final AutofillValue value = dataset.getFieldValues().get(fieldIndex);
if (value == null || !value.isText()) {
return dataset.getAuthentication() == null;
}
final String valueText = value.getTextValue().toString().toLowerCase();
return valueText.toLowerCase().startsWith(constraintLowerCase);
return response;
}
private static InlineSuggestion createInlineSuggestion(boolean isAugmented,

View File

@@ -17,6 +17,7 @@
package com.android.server.autofill.ui;
import static com.android.server.autofill.Helper.sDebug;
import static com.android.server.autofill.Helper.sVerbose;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -42,13 +43,8 @@ import com.android.internal.view.inline.IInlineContentCallback;
* {@link InlineContentProviderImpl}s, each of which wraps a callback from the IME. But at any
* given time, there is only one active IME callback which this class will callback into.
*
* <p>This class is thread safe, because all the outside calls are piped into the same single
* thread handler to be processed.
*
* TODO(b/154683107): implement the reference counting in case there are multiple active
* SurfacePackages at the same time. This will not happen for now since all the InlineSuggestions
* sharing the same UI will be sent to the same IME window, so the previous view will be detached
* before the new view are attached to the window.
* <p>This class is thread safe, because all the outside calls are piped into a single handler
* thread to be processed.
*/
final class RemoteInlineSuggestionUi {
@@ -83,6 +79,7 @@ final class RemoteInlineSuggestionUi {
*/
@Nullable
private IInlineSuggestionUi mInlineSuggestionUi;
private int mRefCount = 0;
private boolean mWaitingForUiCreation = false;
private int mActualWidth;
private int mActualHeight;
@@ -124,7 +121,7 @@ final class RemoteInlineSuggestionUi {
* released.
*/
void surfacePackageReleased() {
mHandler.post(this::handleSurfacePackageReleased);
mHandler.post(() -> handleUpdateRefCount(-1));
}
/**
@@ -134,24 +131,6 @@ final class RemoteInlineSuggestionUi {
return mWidth == width && mHeight == height;
}
private void handleSurfacePackageReleased() {
cancelPendingReleaseViewRequest();
// Schedule a delayed release view request
mDelayedReleaseViewRunnable = () -> {
if (mInlineSuggestionUi != null) {
try {
mInlineSuggestionUi.releaseSurfaceControlViewHost();
mInlineSuggestionUi = null;
} catch (RemoteException e) {
Slog.w(TAG, "RemoteException calling releaseSurfaceControlViewHost");
}
}
mDelayedReleaseViewRunnable = null;
};
mHandler.postDelayed(mDelayedReleaseViewRunnable, RELEASE_REMOTE_VIEW_HOST_DELAY_MS);
}
private void handleRequestSurfacePackage() {
cancelPendingReleaseViewRequest();
@@ -174,10 +153,17 @@ final class RemoteInlineSuggestionUi {
try {
mInlineSuggestionUi.getSurfacePackage(new ISurfacePackageResultCallback.Stub() {
@Override
public void onResult(SurfaceControlViewHost.SurfacePackage result)
throws RemoteException {
if (sDebug) Slog.d(TAG, "Sending new SurfacePackage to IME");
mInlineContentCallback.onContent(result, mActualWidth, mActualHeight);
public void onResult(SurfaceControlViewHost.SurfacePackage result) {
mHandler.post(() -> {
if (sVerbose) Slog.v(TAG, "Sending refreshed SurfacePackage to IME");
try {
mInlineContentCallback.onContent(result, mActualWidth,
mActualHeight);
handleUpdateRefCount(1);
} catch (RemoteException e) {
Slog.w(TAG, "RemoteException calling onContent");
}
});
}
});
} catch (RemoteException e) {
@@ -186,6 +172,26 @@ final class RemoteInlineSuggestionUi {
}
}
private void handleUpdateRefCount(int delta) {
cancelPendingReleaseViewRequest();
mRefCount += delta;
if (mRefCount <= 0) {
mDelayedReleaseViewRunnable = () -> {
if (mInlineSuggestionUi != null) {
try {
if (sVerbose) Slog.v(TAG, "releasing the host");
mInlineSuggestionUi.releaseSurfaceControlViewHost();
mInlineSuggestionUi = null;
} catch (RemoteException e) {
Slog.w(TAG, "RemoteException calling releaseSurfaceControlViewHost");
}
}
mDelayedReleaseViewRunnable = null;
};
mHandler.postDelayed(mDelayedReleaseViewRunnable, RELEASE_REMOTE_VIEW_HOST_DELAY_MS);
}
}
private void cancelPendingReleaseViewRequest() {
if (mDelayedReleaseViewRunnable != null) {
mHandler.removeCallbacks(mDelayedReleaseViewRunnable);
@@ -199,11 +205,14 @@ final class RemoteInlineSuggestionUi {
private void handleInlineSuggestionUiReady(IInlineSuggestionUi content,
SurfaceControlViewHost.SurfacePackage surfacePackage, int width, int height) {
mInlineSuggestionUi = content;
mRefCount = 0;
mWaitingForUiCreation = false;
mActualWidth = width;
mActualHeight = height;
if (mInlineContentCallback != null) {
try {
if (sVerbose) Slog.v(TAG, "Sending new UI content to IME");
handleUpdateRefCount(1);
mInlineContentCallback.onContent(surfacePackage, mActualWidth, mActualHeight);
} catch (RemoteException e) {
Slog.w(TAG, "RemoteException calling onContent");