1 /* 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 17 package android.service.autofill; 18 19 import static android.view.autofill.Helper.sDebug; 20 21 import android.annotation.NonNull; 22 import android.annotation.Nullable; 23 import android.app.assist.AssistStructure; 24 import android.app.assist.AssistStructure.ViewNode; 25 import android.os.Bundle; 26 import android.os.CancellationSignal; 27 import android.os.Parcel; 28 import android.os.Parcelable; 29 import android.util.ArrayMap; 30 import android.util.SparseIntArray; 31 import android.view.autofill.AutofillId; 32 33 import java.util.LinkedList; 34 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 */ 49 public final class FillContext implements Parcelable { 50 private final int mRequestId; 51 private final @NonNull AssistStructure mStructure; 52 53 /** 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 60 /** @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 90 public String toString() { 91 if (!sDebug) return super.toString(); 92 93 return "FillContext [reqId=" + mRequestId + "]"; 94 } 95 96 @Override 97 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 107 /** 108 * Finds {@link ViewNode ViewNodes} that have the requested ids. 109 * 110 * @param ids The ids of the node to find. 111 * 112 * @return The nodes indexed in the same way as the ids. 113 * 114 * @hide 115 */ 116 @NonNull public ViewNode[] findViewNodesByAutofillIds(@NonNull AutofillId[] ids) { 117 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 180 /** 181 * Finds the {@link ViewNode} that has the requested {@code id}, if any. 182 * 183 * @hide 184 */ 185 @Nullable public ViewNode findViewNodeByAutofillId(@NonNull AutofillId id) { 186 final LinkedList<ViewNode> nodesToProcess = new LinkedList<>(); 187 final int numWindowNodes = mStructure.getWindowNodeCount(); 188 for (int i = 0; i < numWindowNodes; i++) { 189 nodesToProcess.add(mStructure.getWindowNodeAt(i).getRootViewNode()); 190 } 191 while (!nodesToProcess.isEmpty()) { 192 final ViewNode node = nodesToProcess.removeFirst(); 193 if (id.equals(node.getAutofillId())) { 194 return node; 195 } 196 for (int i = 0; i < node.getChildCount(); i++) { 197 nodesToProcess.addLast(node.getChildAt(i)); 198 } 199 } 200 201 return null; 202 } 203 204 public static final Parcelable.Creator<FillContext> CREATOR = 205 new Parcelable.Creator<FillContext>() { 206 @Override 207 public FillContext createFromParcel(Parcel parcel) { 208 return new FillContext(parcel); 209 } 210 211 @Override 212 public FillContext[] newArray(int size) { 213 return new FillContext[size]; 214 } 215 }; 216 } 217