Home | History | Annotate | Download | only in autofill
      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     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 }
    193