blob: 535c00bc53196bc5f2392a755f70986235bc3712 [file] [log] [blame]
Svet Ganov013efe12017-04-13 21:56:16 -07001/*
2 * Copyright (C) 2017 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.service.autofill;
18
Felipe Leme9f9ee252017-04-27 13:56:22 -070019import static android.view.autofill.Helper.sDebug;
Philip P. Moltmannc7619632017-04-25 11:57:37 -070020
Svet Ganov013efe12017-04-13 21:56:16 -070021import android.annotation.NonNull;
Philip P. Moltmann22567d32017-05-09 12:56:50 -070022import android.annotation.Nullable;
Svet Ganov013efe12017-04-13 21:56:16 -070023import android.app.assist.AssistStructure;
Philip P. Moltmann22567d32017-05-09 12:56:50 -070024import android.app.assist.AssistStructure.ViewNode;
Svet Ganov013efe12017-04-13 21:56:16 -070025import android.os.Bundle;
26import android.os.CancellationSignal;
27import android.os.Parcel;
28import android.os.Parcelable;
Philip P. Moltmann22567d32017-05-09 12:56:50 -070029import android.util.ArrayMap;
30import android.util.SparseIntArray;
31import android.view.autofill.AutofillId;
32
Philip P. Moltmann22567d32017-05-09 12:56:50 -070033import java.util.LinkedList;
Svet Ganov013efe12017-04-13 21:56:16 -070034
35/**
36 * This class represents a context for each fill request made via {@link
37 * AutofillService#onFillRequest(FillRequest, CancellationSignal, FillCallback)}.
38 * It contains a snapshot of the UI state, the view ids that were returned by
39 * the {@link AutofillService autofill service} as both required to trigger a save
40 * and optional that can be saved, and the id of the corresponding {@link
41 * FillRequest}.
42 * <p>
43 * This context allows you to inspect the values for the interesting views
44 * in the context they appeared. Also a reference to the corresponding fill
45 * request is useful to store meta-data in the client state bundle passed
46 * to {@link FillResponse.Builder#setClientState(Bundle)} to avoid interpreting
47 * the UI state again while saving.
48 */
49public final class FillContext implements Parcelable {
50 private final int mRequestId;
51 private final @NonNull AssistStructure mStructure;
52
Philip P. Moltmann22567d32017-05-09 12:56:50 -070053 /**
54 * Lookup table AutofillId->ViewNode to speed up {@link #findViewNodesByAutofillIds}
55 * This is purely a cache and can be deleted at any time
56 */
57 @Nullable private ArrayMap<AutofillId, AssistStructure.ViewNode> mViewNodeLookupTable;
58
59
Svet Ganov013efe12017-04-13 21:56:16 -070060 /** @hide */
61 public FillContext(int requestId, @NonNull AssistStructure structure) {
62 mRequestId = requestId;
63 mStructure = structure;
64 }
65
66 private FillContext(Parcel parcel) {
67 this(parcel.readInt(), parcel.readParcelable(null));
68 }
69
70 /**
71 * Gets the id of the {@link FillRequest fill request} this context
72 * corresponds to. This is useful to associate your custom client
73 * state with every request to avoid reinterpreting the UI when saving
74 * user data.
75 *
76 * @return The request id.
77 */
78 public int getRequestId() {
79 return mRequestId;
80 }
81
82 /**
83 * @return The screen content.
84 */
85 public AssistStructure getStructure() {
86 return mStructure;
87 }
88
89 @Override
Philip P. Moltmannc7619632017-04-25 11:57:37 -070090 public String toString() {
Felipe Leme9f9ee252017-04-27 13:56:22 -070091 if (!sDebug) return super.toString();
Felipe Leme0aa4c502017-04-26 12:36:01 -070092
93 return "FillContext [reqId=" + mRequestId + "]";
Philip P. Moltmannc7619632017-04-25 11:57:37 -070094 }
95
96 @Override
Svet Ganov013efe12017-04-13 21:56:16 -070097 public int describeContents() {
98 return 0;
99 }
100
101 @Override
102 public void writeToParcel(Parcel parcel, int flags) {
103 parcel.writeInt(mRequestId);
104 parcel.writeParcelable(mStructure, flags);
105 }
106
Philip P. Moltmann22567d32017-05-09 12:56:50 -0700107 /**
Felipe Leme94b56202017-06-16 11:49:48 -0700108 * Finds {@link ViewNode ViewNodes} that have the requested ids.
Philip P. Moltmann22567d32017-05-09 12:56:50 -0700109 *
Felipe Leme94b56202017-06-16 11:49:48 -0700110 * @param ids The ids of the node to find.
Philip P. Moltmann22567d32017-05-09 12:56:50 -0700111 *
Felipe Leme94b56202017-06-16 11:49:48 -0700112 * @return The nodes indexed in the same way as the ids.
Philip P. Moltmann22567d32017-05-09 12:56:50 -0700113 *
114 * @hide
115 */
Felipe Leme94b56202017-06-16 11:49:48 -0700116 @NonNull public ViewNode[] findViewNodesByAutofillIds(@NonNull AutofillId[] ids) {
Philip P. Moltmann22567d32017-05-09 12:56:50 -0700117 final LinkedList<ViewNode> nodesToProcess = new LinkedList<>();
118 final ViewNode[] foundNodes = new AssistStructure.ViewNode[ids.length];
119
120 // Indexes of foundNodes that are not found yet
121 final SparseIntArray missingNodeIndexes = new SparseIntArray(ids.length);
122
123 for (int i = 0; i < ids.length; i++) {
124 if (mViewNodeLookupTable != null) {
125 int lookupTableIndex = mViewNodeLookupTable.indexOfKey(ids[i]);
126
127 if (lookupTableIndex >= 0) {
128 foundNodes[i] = mViewNodeLookupTable.valueAt(lookupTableIndex);
129 } else {
130 missingNodeIndexes.put(i, /* ignored */ 0);
131 }
132 } else {
133 missingNodeIndexes.put(i, /* ignored */ 0);
134 }
135 }
136
137 final int numWindowNodes = mStructure.getWindowNodeCount();
138 for (int i = 0; i < numWindowNodes; i++) {
139 nodesToProcess.add(mStructure.getWindowNodeAt(i).getRootViewNode());
140 }
141
142 while (missingNodeIndexes.size() > 0 && !nodesToProcess.isEmpty()) {
143 final ViewNode node = nodesToProcess.removeFirst();
144
145 for (int i = 0; i < missingNodeIndexes.size(); i++) {
146 final int index = missingNodeIndexes.keyAt(i);
147 final AutofillId id = ids[index];
148
149 if (id.equals(node.getAutofillId())) {
150 foundNodes[index] = node;
151
152 if (mViewNodeLookupTable == null) {
153 mViewNodeLookupTable = new ArrayMap<>(ids.length);
154 }
155
156 mViewNodeLookupTable.put(id, node);
157
158 missingNodeIndexes.removeAt(i);
159 break;
160 }
161 }
162
163 for (int i = 0; i < node.getChildCount(); i++) {
164 nodesToProcess.addLast(node.getChildAt(i));
165 }
166 }
167
168 // Remember which ids could not be resolved to not search for them again the next time
169 for (int i = 0; i < missingNodeIndexes.size(); i++) {
170 if (mViewNodeLookupTable == null) {
171 mViewNodeLookupTable = new ArrayMap<>(missingNodeIndexes.size());
172 }
173
174 mViewNodeLookupTable.put(ids[missingNodeIndexes.keyAt(i)], null);
175 }
176
177 return foundNodes;
178 }
179
Svet Ganov013efe12017-04-13 21:56:16 -0700180 public static final Parcelable.Creator<FillContext> CREATOR =
181 new Parcelable.Creator<FillContext>() {
182 @Override
183 public FillContext createFromParcel(Parcel parcel) {
184 return new FillContext(parcel);
185 }
186
187 @Override
188 public FillContext[] newArray(int size) {
189 return new FillContext[size];
190 }
191 };
192}