Merge changes I396c2cb2,If04eb5ab
* changes: Implement filtering for Inline Autofill. Some cleanup for autofill.Session.updateLocked()
This commit is contained in:
@@ -2436,65 +2436,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
|
||||
forceRemoveSelfLocked(AutofillManager.STATE_UNKNOWN_COMPAT_MODE);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Objects.equals(value, viewState.getCurrentValue())) {
|
||||
if ((value == null || value.isEmpty())
|
||||
&& viewState.getCurrentValue() != null
|
||||
&& viewState.getCurrentValue().isText()
|
||||
&& viewState.getCurrentValue().getTextValue() != null
|
||||
&& getSaveInfoLocked() != null) {
|
||||
final int length = viewState.getCurrentValue().getTextValue().length();
|
||||
if (sDebug) {
|
||||
Slog.d(TAG, "updateLocked(" + id + "): resetting value that was "
|
||||
+ length + " chars long");
|
||||
}
|
||||
final LogMaker log = newLogMaker(MetricsEvent.AUTOFILL_VALUE_RESET)
|
||||
.addTaggedData(MetricsEvent.FIELD_AUTOFILL_PREVIOUS_LENGTH, length);
|
||||
mMetricsLogger.write(log);
|
||||
}
|
||||
|
||||
// Always update the internal state.
|
||||
viewState.setCurrentValue(value);
|
||||
|
||||
// Must check if this update was caused by autofilling the view, in which
|
||||
// case we just update the value, but not the UI.
|
||||
final AutofillValue filledValue = viewState.getAutofilledValue();
|
||||
if (filledValue != null) {
|
||||
if (filledValue.equals(value)) {
|
||||
if (sVerbose) {
|
||||
Slog.v(TAG, "ignoring autofilled change on id " + id);
|
||||
}
|
||||
viewState.resetState(ViewState.STATE_CHANGED);
|
||||
return;
|
||||
}
|
||||
else {
|
||||
if ((viewState.id.equals(this.mCurrentViewId)) &&
|
||||
(viewState.getState() & ViewState.STATE_AUTOFILLED) != 0) {
|
||||
// Remove autofilled state once field is changed after autofilling.
|
||||
if (sVerbose) {
|
||||
Slog.v(TAG, "field changed after autofill on id " + id);
|
||||
}
|
||||
viewState.resetState(ViewState.STATE_AUTOFILLED);
|
||||
final ViewState currentView = mViewStates.get(mCurrentViewId);
|
||||
currentView.maybeCallOnFillReady(flags);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update the internal state...
|
||||
viewState.setState(ViewState.STATE_CHANGED);
|
||||
|
||||
//..and the UI
|
||||
final String filterText;
|
||||
if (value == null || !value.isText()) {
|
||||
filterText = null;
|
||||
} else {
|
||||
final CharSequence text = value.getTextValue();
|
||||
// Text should never be null, but it doesn't hurt to check to avoid a
|
||||
// system crash...
|
||||
filterText = (text == null) ? null : text.toString();
|
||||
}
|
||||
getUiForShowing().filterFillUi(filterText, this);
|
||||
logIfViewClearedLocked(id, value, viewState);
|
||||
updateViewStateAndUiOnValueChangedLocked(id, value, viewState, flags);
|
||||
}
|
||||
break;
|
||||
case ACTION_VIEW_ENTERED:
|
||||
@@ -2573,6 +2517,68 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
|
||||
return ArrayUtils.contains(response.getIgnoredIds(), id);
|
||||
}
|
||||
|
||||
@GuardedBy("mLock")
|
||||
private void logIfViewClearedLocked(AutofillId id, AutofillValue value, ViewState viewState) {
|
||||
if ((value == null || value.isEmpty())
|
||||
&& viewState.getCurrentValue() != null
|
||||
&& viewState.getCurrentValue().isText()
|
||||
&& viewState.getCurrentValue().getTextValue() != null
|
||||
&& getSaveInfoLocked() != null) {
|
||||
final int length = viewState.getCurrentValue().getTextValue().length();
|
||||
if (sDebug) {
|
||||
Slog.d(TAG, "updateLocked(" + id + "): resetting value that was "
|
||||
+ length + " chars long");
|
||||
}
|
||||
final LogMaker log = newLogMaker(MetricsEvent.AUTOFILL_VALUE_RESET)
|
||||
.addTaggedData(MetricsEvent.FIELD_AUTOFILL_PREVIOUS_LENGTH, length);
|
||||
mMetricsLogger.write(log);
|
||||
}
|
||||
}
|
||||
|
||||
@GuardedBy("mLock")
|
||||
private void updateViewStateAndUiOnValueChangedLocked(AutofillId id, AutofillValue value,
|
||||
ViewState viewState, int flags) {
|
||||
viewState.setCurrentValue(value);
|
||||
|
||||
final String filterText;
|
||||
if (value == null || !value.isText()) {
|
||||
filterText = null;
|
||||
} else {
|
||||
final CharSequence text = value.getTextValue();
|
||||
// Text should never be null, but it doesn't hurt to check to avoid a
|
||||
// system crash...
|
||||
filterText = (text == null) ? null : text.toString();
|
||||
}
|
||||
|
||||
final AutofillValue filledValue = viewState.getAutofilledValue();
|
||||
if (filledValue != null) {
|
||||
if (filledValue.equals(value)) {
|
||||
// When the update is caused by autofilling the view, just update the
|
||||
// value, not the UI.
|
||||
if (sVerbose) {
|
||||
Slog.v(TAG, "ignoring autofilled change on id " + id);
|
||||
}
|
||||
viewState.resetState(ViewState.STATE_CHANGED);
|
||||
return;
|
||||
} else if ((viewState.id.equals(this.mCurrentViewId))
|
||||
&& (viewState.getState() & ViewState.STATE_AUTOFILLED) != 0) {
|
||||
// Remove autofilled state once field is changed after autofilling.
|
||||
if (sVerbose) {
|
||||
Slog.v(TAG, "field changed after autofill on id " + id);
|
||||
}
|
||||
viewState.resetState(ViewState.STATE_AUTOFILLED);
|
||||
final ViewState currentView = mViewStates.get(mCurrentViewId);
|
||||
currentView.maybeCallOnFillReady(flags);
|
||||
}
|
||||
} else if (viewState.id.equals(this.mCurrentViewId)
|
||||
&& (viewState.getState() & ViewState.STATE_INLINE_SHOWN) != 0) {
|
||||
requestShowInlineSuggestionsLocked(viewState.getResponse(), filterText);
|
||||
}
|
||||
|
||||
viewState.setState(ViewState.STATE_CHANGED);
|
||||
getUiForShowing().filterFillUi(filterText, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFillReady(@NonNull FillResponse response, @NonNull AutofillId filledId,
|
||||
@Nullable AutofillValue value) {
|
||||
@@ -2602,7 +2608,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
|
||||
|
||||
if (response.supportsInlineSuggestions()) {
|
||||
synchronized (mLock) {
|
||||
if (requestShowInlineSuggestionsLocked(response)) {
|
||||
if (requestShowInlineSuggestionsLocked(response, filterText)) {
|
||||
final ViewState currentView = mViewStates.get(mCurrentViewId);
|
||||
currentView.setState(ViewState.STATE_INLINE_SHOWN);
|
||||
//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);
|
||||
@@ -2645,7 +2653,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
|
||||
/**
|
||||
* Returns whether we made a request to show inline suggestions.
|
||||
*/
|
||||
private boolean requestShowInlineSuggestionsLocked(@NonNull FillResponse response) {
|
||||
private boolean requestShowInlineSuggestionsLocked(@NonNull FillResponse response,
|
||||
@Nullable String filterText) {
|
||||
final List<Dataset> datasets = response.getDatasets();
|
||||
if (datasets == null) {
|
||||
Log.w(TAG, "response returned null datasets");
|
||||
@@ -2663,7 +2672,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
|
||||
InlineSuggestionsResponse inlineSuggestionsResponse =
|
||||
InlineSuggestionFactory.createInlineSuggestionsResponse(request,
|
||||
response.getRequestId(),
|
||||
datasets.toArray(new Dataset[]{}), response.getInlineActions(),
|
||||
datasets.toArray(new Dataset[]{}), filterText, response.getInlineActions(),
|
||||
mCurrentViewId, mContext, this, () -> {
|
||||
synchronized (mLock) {
|
||||
requestHideFillUi(mCurrentViewId);
|
||||
|
||||
@@ -74,6 +74,8 @@ final class ViewState {
|
||||
public static final int STATE_AUTOFILLED_ONCE = 0x800;
|
||||
/** View triggered the latest augmented autofill request. */
|
||||
public static final int STATE_TRIGGERED_AUGMENTED_AUTOFILL = 0x1000;
|
||||
/** Inline suggestions were shown for this View. */
|
||||
public static final int STATE_INLINE_SHOWN = 0x2000;
|
||||
|
||||
public final AutofillId id;
|
||||
|
||||
|
||||
@@ -313,6 +313,8 @@ final class FillUi {
|
||||
Slog.e(TAG, "Error inflating remote views", e);
|
||||
continue;
|
||||
}
|
||||
// TODO: Extract the shared filtering logic here and in FillUi to a common
|
||||
// method.
|
||||
final DatasetFieldFilter filter = dataset.getFilter(index);
|
||||
Pattern filterPattern = null;
|
||||
String valueText = null;
|
||||
@@ -602,6 +604,7 @@ final class FillUi {
|
||||
* Returns whether this item matches the value input by the user so it can be included
|
||||
* in the filtered datasets.
|
||||
*/
|
||||
// TODO: Extract the shared filtering logic here and in FillUi to a common method.
|
||||
public boolean matches(CharSequence filterText) {
|
||||
if (TextUtils.isEmpty(filterText)) {
|
||||
// Always show item when the user input is empty
|
||||
|
||||
@@ -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;
|
||||
@@ -24,10 +25,12 @@ import android.content.Context;
|
||||
import android.os.RemoteException;
|
||||
import android.service.autofill.Dataset;
|
||||
import android.service.autofill.InlinePresentation;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Slog;
|
||||
import android.view.SurfaceControl;
|
||||
import android.view.View;
|
||||
import android.view.autofill.AutofillId;
|
||||
import android.view.autofill.AutofillValue;
|
||||
import android.view.inline.InlinePresentationSpec;
|
||||
import android.view.inputmethod.InlineSuggestion;
|
||||
import android.view.inputmethod.InlineSuggestionInfo;
|
||||
@@ -42,6 +45,7 @@ import com.android.server.UiThread;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public final class InlineSuggestionFactory {
|
||||
private static final String TAG = "InlineSuggestionFactory";
|
||||
@@ -69,17 +73,19 @@ public final class InlineSuggestionFactory {
|
||||
@NonNull Runnable onErrorCallback) {
|
||||
if (sDebug) Slog.d(TAG, "createAugmentedInlineSuggestionsResponse called");
|
||||
return createInlineSuggestionsResponseInternal(/* isAugmented= */ true, request,
|
||||
datasets, /* inlineActions= */ null, autofillId, context, onErrorCallback,
|
||||
datasets, /* filterText= */ null, /* inlineActions= */ null, autofillId, context,
|
||||
onErrorCallback,
|
||||
(dataset, filedIndex) -> (v -> inlineSuggestionUiCallback.autofill(dataset)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an {@link InlineSuggestionsResponse} with the {@code datasets} provided by the
|
||||
* autofill service.
|
||||
* autofill service, potentially filtering the datasets.
|
||||
*/
|
||||
public static InlineSuggestionsResponse createInlineSuggestionsResponse(
|
||||
@NonNull InlineSuggestionsRequest request, int requestId,
|
||||
@NonNull Dataset[] datasets,
|
||||
@Nullable String filterText,
|
||||
@Nullable List<InlinePresentation> inlineActions,
|
||||
@NonNull AutofillId autofillId,
|
||||
@NonNull Context context,
|
||||
@@ -87,15 +93,15 @@ public final class InlineSuggestionFactory {
|
||||
@NonNull Runnable onErrorCallback) {
|
||||
if (sDebug) Slog.d(TAG, "createInlineSuggestionsResponse called");
|
||||
return createInlineSuggestionsResponseInternal(/* isAugmented= */ false, request, datasets,
|
||||
inlineActions, autofillId, context, onErrorCallback,
|
||||
filterText, inlineActions, autofillId, context, onErrorCallback,
|
||||
(dataset, filedIndex) -> (v -> client.fill(requestId, filedIndex, dataset)));
|
||||
}
|
||||
|
||||
private static InlineSuggestionsResponse createInlineSuggestionsResponseInternal(
|
||||
boolean isAugmented, @NonNull InlineSuggestionsRequest request,
|
||||
@NonNull Dataset[] datasets, @Nullable List<InlinePresentation> inlineActions,
|
||||
@NonNull AutofillId autofillId, @NonNull Context context,
|
||||
@NonNull Runnable onErrorCallback,
|
||||
@NonNull Dataset[] datasets, @Nullable String filterText,
|
||||
@Nullable List<InlinePresentation> inlineActions, @NonNull AutofillId autofillId,
|
||||
@NonNull Context context, @NonNull Runnable onErrorCallback,
|
||||
@NonNull BiFunction<Dataset, Integer, View.OnClickListener> onClickListenerFactory) {
|
||||
final ArrayList<InlineSuggestion> inlineSuggestions = new ArrayList<>();
|
||||
final InlineSuggestionUi inlineSuggestionUi = new InlineSuggestionUi(context,
|
||||
@@ -113,6 +119,9 @@ public final class InlineSuggestionFactory {
|
||||
Slog.w(TAG, "InlinePresentation not found in dataset");
|
||||
return null;
|
||||
}
|
||||
if (!includeDataset(dataset, fieldIndex, filterText)) {
|
||||
continue;
|
||||
}
|
||||
InlineSuggestion inlineSuggestion = createInlineSuggestion(isAugmented, dataset,
|
||||
fieldIndex, mergedInlinePresentation(request, i, inlinePresentation),
|
||||
inlineSuggestionUi, onClickListenerFactory);
|
||||
@@ -129,6 +138,38 @@ public final class InlineSuggestionFactory {
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
private static InlineSuggestion createInlineAction(boolean isAugmented,
|
||||
@NonNull Context context,
|
||||
@NonNull InlinePresentation inlinePresentation,
|
||||
|
||||
Reference in New Issue
Block a user