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 private final @NonNull AutofillId mFocusedId; 53 54 /** 55 * Lookup table AutofillId->ViewNode to speed up {@link #findViewNodesByAutofillIds} 56 * This is purely a cache and can be deleted at any time 57 */ 58 @Nullable private ArrayMap<AutofillId, AssistStructure.ViewNode> mViewNodeLookupTable; 59 60 61 /** @hide */ FillContext(int requestId, @NonNull AssistStructure structure, @NonNull AutofillId autofillId)62 public FillContext(int requestId, @NonNull AssistStructure structure, 63 @NonNull AutofillId autofillId) { 64 mRequestId = requestId; 65 mStructure = structure; 66 mFocusedId = autofillId; 67 } 68 FillContext(Parcel parcel)69 private FillContext(Parcel parcel) { 70 this(parcel.readInt(), parcel.readParcelable(null), parcel.readParcelable(null)); 71 } 72 73 /** 74 * Gets the id of the {@link FillRequest fill request} this context 75 * corresponds to. This is useful to associate your custom client 76 * state with every request to avoid reinterpreting the UI when saving 77 * user data. 78 * 79 * @return The request id. 80 */ getRequestId()81 public int getRequestId() { 82 return mRequestId; 83 } 84 85 /** 86 * @return The screen content. 87 */ 88 @NonNull getStructure()89 public AssistStructure getStructure() { 90 return mStructure; 91 } 92 93 /** 94 * @return the AutofillId of the view that triggered autofill. 95 */ 96 @NonNull getFocusedId()97 public AutofillId getFocusedId() { 98 return mFocusedId; 99 } 100 101 @Override toString()102 public String toString() { 103 if (!sDebug) return super.toString(); 104 105 return "FillContext [reqId=" + mRequestId + ", focusedId=" + mFocusedId + "]"; 106 } 107 108 @Override describeContents()109 public int describeContents() { 110 return 0; 111 } 112 113 @Override writeToParcel(Parcel parcel, int flags)114 public void writeToParcel(Parcel parcel, int flags) { 115 parcel.writeInt(mRequestId); 116 parcel.writeParcelable(mStructure, flags); 117 parcel.writeParcelable(mFocusedId, flags); 118 } 119 120 /** 121 * Finds {@link ViewNode ViewNodes} that have the requested ids. 122 * 123 * @param ids The ids of the node to find. 124 * 125 * @return The nodes indexed in the same way as the ids. 126 * 127 * @hide 128 */ findViewNodesByAutofillIds(@onNull AutofillId[] ids)129 @NonNull public ViewNode[] findViewNodesByAutofillIds(@NonNull AutofillId[] ids) { 130 final LinkedList<ViewNode> nodesToProcess = new LinkedList<>(); 131 final ViewNode[] foundNodes = new AssistStructure.ViewNode[ids.length]; 132 133 // Indexes of foundNodes that are not found yet 134 final SparseIntArray missingNodeIndexes = new SparseIntArray(ids.length); 135 136 for (int i = 0; i < ids.length; i++) { 137 if (mViewNodeLookupTable != null) { 138 int lookupTableIndex = mViewNodeLookupTable.indexOfKey(ids[i]); 139 140 if (lookupTableIndex >= 0) { 141 foundNodes[i] = mViewNodeLookupTable.valueAt(lookupTableIndex); 142 } else { 143 missingNodeIndexes.put(i, /* ignored */ 0); 144 } 145 } else { 146 missingNodeIndexes.put(i, /* ignored */ 0); 147 } 148 } 149 150 final int numWindowNodes = mStructure.getWindowNodeCount(); 151 for (int i = 0; i < numWindowNodes; i++) { 152 nodesToProcess.add(mStructure.getWindowNodeAt(i).getRootViewNode()); 153 } 154 155 while (missingNodeIndexes.size() > 0 && !nodesToProcess.isEmpty()) { 156 final ViewNode node = nodesToProcess.removeFirst(); 157 158 for (int i = 0; i < missingNodeIndexes.size(); i++) { 159 final int index = missingNodeIndexes.keyAt(i); 160 final AutofillId id = ids[index]; 161 162 if (id.equals(node.getAutofillId())) { 163 foundNodes[index] = node; 164 165 if (mViewNodeLookupTable == null) { 166 mViewNodeLookupTable = new ArrayMap<>(ids.length); 167 } 168 169 mViewNodeLookupTable.put(id, node); 170 171 missingNodeIndexes.removeAt(i); 172 break; 173 } 174 } 175 176 for (int i = 0; i < node.getChildCount(); i++) { 177 nodesToProcess.addLast(node.getChildAt(i)); 178 } 179 } 180 181 // Remember which ids could not be resolved to not search for them again the next time 182 for (int i = 0; i < missingNodeIndexes.size(); i++) { 183 if (mViewNodeLookupTable == null) { 184 mViewNodeLookupTable = new ArrayMap<>(missingNodeIndexes.size()); 185 } 186 187 mViewNodeLookupTable.put(ids[missingNodeIndexes.keyAt(i)], null); 188 } 189 190 return foundNodes; 191 } 192 193 public static final @android.annotation.NonNull Parcelable.Creator<FillContext> CREATOR = 194 new Parcelable.Creator<FillContext>() { 195 @Override 196 @NonNull 197 public FillContext createFromParcel(Parcel parcel) { 198 return new FillContext(parcel); 199 } 200 201 @Override 202 @NonNull 203 public FillContext[] newArray(int size) { 204 return new FillContext[size]; 205 } 206 }; 207 } 208