Merge "Speed up structure update before OnFillRequest" into oc-dev
diff --git a/core/java/android/service/autofill/FillContext.java b/core/java/android/service/autofill/FillContext.java
index d6616bf..251d346 100644
--- a/core/java/android/service/autofill/FillContext.java
+++ b/core/java/android/service/autofill/FillContext.java
@@ -19,11 +19,19 @@
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 @@
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 @@
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 8d947b9..ffcde8d 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 @@
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 c455eda..ef5cdd1 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_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.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 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 @@
};
/**
- * 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 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;