diff options
3 files changed, 121 insertions, 53 deletions
diff --git a/core/java/android/service/autofill/FillContext.java b/core/java/android/service/autofill/FillContext.java index d6616bfb8d66..251d346efb42 100644 --- a/core/java/android/service/autofill/FillContext.java +++ b/core/java/android/service/autofill/FillContext.java @@ -19,11 +19,19 @@ package android.service.autofill; import static android.view.autofill.Helper.sDebug; import android.annotation.NonNull; +import android.annotation.Nullable; import android.app.assist.AssistStructure; +import android.app.assist.AssistStructure.ViewNode; import android.os.Bundle; import android.os.CancellationSignal; import android.os.Parcel; import android.os.Parcelable; +import android.util.ArrayMap; +import android.util.SparseIntArray; +import android.view.autofill.AutofillId; + +import java.util.ArrayList; +import java.util.LinkedList; /** * This class represents a context for each fill request made via {@link @@ -43,6 +51,13 @@ public final class FillContext implements Parcelable { private final int mRequestId; private final @NonNull AssistStructure mStructure; + /** + * Lookup table AutofillId->ViewNode to speed up {@link #findViewNodesByAutofillIds} + * This is purely a cache and can be deleted at any time + */ + @Nullable private ArrayMap<AutofillId, AssistStructure.ViewNode> mViewNodeLookupTable; + + /** @hide */ public FillContext(int requestId, @NonNull AssistStructure structure) { mRequestId = requestId; @@ -90,6 +105,79 @@ public final class FillContext implements Parcelable { parcel.writeParcelable(mStructure, flags); } + /** + * Finds {@link ViewNode}s that have the requested ids. + * + * @param ids The ids of the node to find + * + * @return The nodes indexed in the same way as the ids + * + * @hide + */ + @NonNull public ViewNode[] findViewNodesByAutofillIds(@NonNull AutofillId[] ids) { + final LinkedList<ViewNode> nodesToProcess = new LinkedList<>(); + final ViewNode[] foundNodes = new AssistStructure.ViewNode[ids.length]; + + // Indexes of foundNodes that are not found yet + final SparseIntArray missingNodeIndexes = new SparseIntArray(ids.length); + + for (int i = 0; i < ids.length; i++) { + if (mViewNodeLookupTable != null) { + int lookupTableIndex = mViewNodeLookupTable.indexOfKey(ids[i]); + + if (lookupTableIndex >= 0) { + foundNodes[i] = mViewNodeLookupTable.valueAt(lookupTableIndex); + } else { + missingNodeIndexes.put(i, /* ignored */ 0); + } + } else { + missingNodeIndexes.put(i, /* ignored */ 0); + } + } + + final int numWindowNodes = mStructure.getWindowNodeCount(); + for (int i = 0; i < numWindowNodes; i++) { + nodesToProcess.add(mStructure.getWindowNodeAt(i).getRootViewNode()); + } + + while (missingNodeIndexes.size() > 0 && !nodesToProcess.isEmpty()) { + final ViewNode node = nodesToProcess.removeFirst(); + + for (int i = 0; i < missingNodeIndexes.size(); i++) { + final int index = missingNodeIndexes.keyAt(i); + final AutofillId id = ids[index]; + + if (id.equals(node.getAutofillId())) { + foundNodes[index] = node; + + if (mViewNodeLookupTable == null) { + mViewNodeLookupTable = new ArrayMap<>(ids.length); + } + + mViewNodeLookupTable.put(id, node); + + missingNodeIndexes.removeAt(i); + break; + } + } + + for (int i = 0; i < node.getChildCount(); i++) { + nodesToProcess.addLast(node.getChildAt(i)); + } + } + + // Remember which ids could not be resolved to not search for them again the next time + for (int i = 0; i < missingNodeIndexes.size(); i++) { + if (mViewNodeLookupTable == null) { + mViewNodeLookupTable = new ArrayMap<>(missingNodeIndexes.size()); + } + + mViewNodeLookupTable.put(ids[missingNodeIndexes.keyAt(i)], null); + } + + return foundNodes; + } + public static final Parcelable.Creator<FillContext> CREATOR = new Parcelable.Creator<FillContext>() { @Override diff --git a/services/autofill/java/com/android/server/autofill/Helper.java b/services/autofill/java/com/android/server/autofill/Helper.java index 8d947b92159c..ffcde8de8448 100644 --- a/services/autofill/java/com/android/server/autofill/Helper.java +++ b/services/autofill/java/com/android/server/autofill/Helper.java @@ -16,11 +16,7 @@ package com.android.server.autofill; -import android.annotation.NonNull; -import android.app.assist.AssistStructure; -import android.app.assist.AssistStructure.ViewNode; import android.os.Bundle; -import android.view.autofill.AutofillId; import java.util.Arrays; import java.util.Objects; @@ -65,37 +61,4 @@ public final class Helper { append(builder, bundle); return builder.toString(); } - - static ViewNode findViewNodeById(@NonNull AssistStructure structure, @NonNull AutofillId id) { - final int size = structure.getWindowNodeCount(); - for (int i = 0; i < size; i++) { - final AssistStructure.WindowNode window = structure.getWindowNodeAt(i); - final ViewNode root = window.getRootViewNode(); - if (id.equals(root.getAutofillId())) { - return root; - } - final ViewNode child = findViewNodeById(root, id); - if (child != null) { - return child; - } - } - return null; - } - - static ViewNode findViewNodeById(@NonNull ViewNode parent, @NonNull AutofillId id) { - final int childrenSize = parent.getChildCount(); - if (childrenSize > 0) { - for (int i = 0; i < childrenSize; i++) { - final ViewNode child = parent.getChildAt(i); - if (id.equals(child.getAutofillId())) { - return child; - } - final ViewNode grandChild = findViewNodeById(child, id); - if (grandChild != null && id.equals(grandChild.getAutofillId())) { - return grandChild; - } - } - } - return null; - } } diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index c455edac66a8..ef5cdd146737 100644 --- a/services/autofill/java/com/android/server/autofill/Session.java +++ b/services/autofill/java/com/android/server/autofill/Session.java @@ -25,7 +25,6 @@ import static android.view.autofill.AutofillManager.ACTION_VALUE_CHANGED; import static android.view.autofill.AutofillManager.ACTION_VIEW_ENTERED; import static android.view.autofill.AutofillManager.ACTION_VIEW_EXITED; -import static com.android.server.autofill.Helper.findViewNodeById; import static com.android.server.autofill.Helper.sDebug; import static com.android.server.autofill.Helper.sVerbose; import static com.android.server.autofill.ViewState.STATE_AUTOFILLED; @@ -79,7 +78,6 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; -import java.util.Map.Entry; import java.util.concurrent.atomic.AtomicInteger; /** @@ -212,7 +210,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState final int numContexts = mContexts.size(); for (int i = 0; i < numContexts; i++) { - fillStructureWithAllowedValues(mContexts.get(i).getStructure(), flags); + fillContextWithAllowedValues(mContexts.get(i), flags); } request = new FillRequest(requestId, mContexts, mClientState, flags); @@ -223,20 +221,35 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState }; /** - * Updates values of the nodes in the structure so that: + * Returns the ids of all entries in {@link #mViewStates} in the same order. + */ + private AutofillId[] getIdsOfAllViewStates() { + final int numViewState = mViewStates.size(); + final AutofillId[] ids = new AutofillId[numViewState]; + for (int i = 0; i < numViewState; i++) { + ids[i] = mViewStates.valueAt(i).id; + } + + return ids; + } + + /** + * Updates values of the nodes in the context's structure so that: * - proper node is focused * - autofillValue is sent back to service when it was previously autofilled * - autofillValue is sent in the view used to force a request * - * @param structure The structure to be filled + * @param fillContext The context to be filled * @param flags The flags that started the session */ - private void fillStructureWithAllowedValues(@NonNull AssistStructure structure, int flags) { - final int numViewStates = mViewStates.size(); - for (int i = 0; i < numViewStates; i++) { + private void fillContextWithAllowedValues(@NonNull FillContext fillContext, int flags) { + final ViewNode[] nodes = fillContext.findViewNodesByAutofillIds(getIdsOfAllViewStates()); + + final int numViewState = mViewStates.size(); + for (int i = 0; i < numViewState; i++) { final ViewState viewState = mViewStates.valueAt(i); - final ViewNode node = findViewNodeById(structure, viewState.id); + final ViewNode node = nodes[i]; if (node == null) { Slog.w(TAG, "fillStructureWithAllowedValues(): no node for " + viewState.id); continue; @@ -841,19 +854,23 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState final int numContexts = mContexts.size(); - for (int i = 0; i < numContexts; i++) { - final FillContext context = mContexts.get(i); + for (int contextNum = 0; contextNum < numContexts; contextNum++) { + final FillContext context = mContexts.get(contextNum); + + final ViewNode[] nodes = context.findViewNodesByAutofillIds(getIdsOfAllViewStates()); if (sVerbose) Slog.v(TAG, "callSaveLocked(): updating " + context); - for (Entry<AutofillId, ViewState> entry : mViewStates.entrySet()) { - final AutofillValue value = entry.getValue().getCurrentValue(); + for (int viewStateNum = 0; viewStateNum < mViewStates.size(); viewStateNum++) { + final ViewState state = mViewStates.valueAt(viewStateNum); + + final AutofillId id = state.id; + final AutofillValue value = state.getCurrentValue(); if (value == null) { - if (sVerbose) Slog.v(TAG, "callSaveLocked(): skipping " + entry.getKey()); + if (sVerbose) Slog.v(TAG, "callSaveLocked(): skipping " + id); continue; } - final AutofillId id = entry.getKey(); - final ViewNode node = findViewNodeById(context.getStructure(), id); + final ViewNode node = nodes[viewStateNum]; if (node == null) { Slog.w(TAG, "callSaveLocked(): did not find node with id " + id); continue; |