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.autofillservice.cts.testcore;
18 
19 import static android.autofillservice.cts.testcore.UiBot.PORTRAIT;
20 import static android.provider.Settings.Secure.AUTOFILL_SERVICE;
21 import static android.provider.Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE;
22 import static android.provider.Settings.Secure.USER_SETUP_COMPLETE;
23 import static android.service.autofill.FillEventHistory.Event.TYPE_AUTHENTICATION_SELECTED;
24 import static android.service.autofill.FillEventHistory.Event.TYPE_CONTEXT_COMMITTED;
25 import static android.service.autofill.FillEventHistory.Event.TYPE_DATASETS_SHOWN;
26 import static android.service.autofill.FillEventHistory.Event.TYPE_DATASET_AUTHENTICATION_SELECTED;
27 import static android.service.autofill.FillEventHistory.Event.TYPE_DATASET_SELECTED;
28 import static android.service.autofill.FillEventHistory.Event.TYPE_SAVE_SHOWN;
29 
30 import static com.android.compatibility.common.util.ShellUtils.runShellCommand;
31 
32 import static com.google.common.truth.Truth.assertThat;
33 import static com.google.common.truth.Truth.assertWithMessage;
34 
35 import android.app.Activity;
36 import android.app.Instrumentation;
37 import android.app.PendingIntent;
38 import android.app.assist.AssistStructure;
39 import android.app.assist.AssistStructure.ViewNode;
40 import android.app.assist.AssistStructure.WindowNode;
41 import android.autofillservice.cts.R;
42 import android.content.AutofillOptions;
43 import android.content.ComponentName;
44 import android.content.ContentResolver;
45 import android.content.Context;
46 import android.content.Intent;
47 import android.content.pm.PackageManager;
48 import android.content.res.Resources;
49 import android.graphics.Bitmap;
50 import android.icu.util.Calendar;
51 import android.os.Bundle;
52 import android.os.Environment;
53 import android.provider.Settings;
54 import android.service.autofill.FieldClassification;
55 import android.service.autofill.FieldClassification.Match;
56 import android.service.autofill.FillContext;
57 import android.service.autofill.FillEventHistory;
58 import android.service.autofill.InlinePresentation;
59 import android.text.TextUtils;
60 import android.util.Log;
61 import android.util.Pair;
62 import android.util.Size;
63 import android.view.View;
64 import android.view.ViewGroup;
65 import android.view.ViewStructure.HtmlInfo;
66 import android.view.autofill.AutofillId;
67 import android.view.autofill.AutofillManager;
68 import android.view.autofill.AutofillManager.AutofillCallback;
69 import android.view.autofill.AutofillValue;
70 import android.webkit.WebView;
71 import android.widget.RemoteViews;
72 import android.widget.inline.InlinePresentationSpec;
73 
74 import androidx.annotation.NonNull;
75 import androidx.annotation.Nullable;
76 import androidx.autofill.inline.v1.InlineSuggestionUi;
77 import androidx.test.platform.app.InstrumentationRegistry;
78 
79 import com.android.compatibility.common.util.BitmapUtils;
80 import com.android.compatibility.common.util.OneTimeSettingsListener;
81 import com.android.compatibility.common.util.SettingsUtils;
82 import com.android.compatibility.common.util.ShellUtils;
83 import com.android.compatibility.common.util.TestNameUtils;
84 import com.android.compatibility.common.util.Timeout;
85 
86 import java.io.File;
87 import java.io.IOException;
88 import java.util.List;
89 import java.util.Map;
90 import java.util.Map.Entry;
91 import java.util.concurrent.BlockingQueue;
92 import java.util.concurrent.TimeUnit;
93 import java.util.function.Function;
94 import java.util.regex.Pattern;
95 
96 /**
97  * Helper for common funcionalities.
98  */
99 public final class Helper {
100 
101     public static final String TAG = "AutoFillCtsHelper";
102 
103     public static final boolean VERBOSE = false;
104 
105     public static final String MY_PACKAGE = "android.autofillservice.cts";
106 
107     public static final String ID_USERNAME_LABEL = "username_label";
108     public static final String ID_USERNAME = "username";
109     public static final String ID_PASSWORD_LABEL = "password_label";
110     public static final String ID_PASSWORD = "password";
111     public static final String ID_LOGIN = "login";
112     public static final String ID_OUTPUT = "output";
113     public static final String ID_STATIC_TEXT = "static_text";
114     public static final String ID_EMPTY = "empty";
115     public static final String ID_CANCEL_FILL = "cancel_fill";
116 
117     public static final String NULL_DATASET_ID = null;
118 
119     public static final char LARGE_STRING_CHAR = '6';
120     // NOTE: cannot be much large as it could ANR and fail the test.
121     public static final int LARGE_STRING_SIZE = 100_000;
122     public static final String LARGE_STRING = com.android.compatibility.common.util.TextUtils
123             .repeat(LARGE_STRING_CHAR, LARGE_STRING_SIZE);
124 
125     /**
126      * Can be used in cases where the autofill values is required by irrelevant (like adding a
127      * value to an authenticated dataset).
128      */
129     public static final String UNUSED_AUTOFILL_VALUE = null;
130 
131     private static final String ACCELLEROMETER_CHANGE =
132             "content insert --uri content://settings/system --bind name:s:accelerometer_rotation "
133                     + "--bind value:i:%d";
134 
135     private static final String LOCAL_DIRECTORY = Environment.getExternalStorageDirectory()
136             + "/CtsAutoFillServiceTestCases";
137 
138     private static final Timeout SETTINGS_BASED_SHELL_CMD_TIMEOUT = new Timeout(
139             "SETTINGS_SHELL_CMD_TIMEOUT", OneTimeSettingsListener.DEFAULT_TIMEOUT_MS / 2, 2,
140             OneTimeSettingsListener.DEFAULT_TIMEOUT_MS);
141 
142     /**
143      * Helper interface used to filter nodes.
144      *
145      * @param <T> node type
146      */
147     interface NodeFilter<T> {
148         /**
149          * Returns whether the node passes the filter for such given id.
150          */
matches(T node, Object id)151         boolean matches(T node, Object id);
152     }
153 
154     private static final NodeFilter<ViewNode> RESOURCE_ID_FILTER = (node, id) -> {
155         return id.equals(node.getIdEntry());
156     };
157 
158     private static final NodeFilter<ViewNode> HTML_NAME_FILTER = (node, id) -> {
159         return id.equals(getHtmlName(node));
160     };
161 
162     private static final NodeFilter<ViewNode> HTML_NAME_OR_RESOURCE_ID_FILTER = (node, id) -> {
163         return id.equals(getHtmlName(node)) || id.equals(node.getIdEntry());
164     };
165 
166     private static final NodeFilter<ViewNode> TEXT_FILTER = (node, id) -> {
167         return id.equals(node.getText());
168     };
169 
170     private static final NodeFilter<ViewNode> AUTOFILL_HINT_FILTER = (node, id) -> {
171         return hasHint(node.getAutofillHints(), id);
172     };
173 
174     private static final NodeFilter<ViewNode> WEBVIEW_FORM_FILTER = (node, id) -> {
175         final String className = node.getClassName();
176         if (!className.equals("android.webkit.WebView")) return false;
177 
178         final HtmlInfo htmlInfo = assertHasHtmlTag(node, "form");
179         final String formName = getAttributeValue(htmlInfo, "name");
180         return id.equals(formName);
181     };
182 
183     private static final NodeFilter<View> AUTOFILL_HINT_VIEW_FILTER = (view, id) -> {
184         return hasHint(view.getAutofillHints(), id);
185     };
186 
toString(AssistStructure structure, StringBuilder builder)187     private static String toString(AssistStructure structure, StringBuilder builder) {
188         builder.append("[component=").append(structure.getActivityComponent());
189         final int nodes = structure.getWindowNodeCount();
190         for (int i = 0; i < nodes; i++) {
191             final WindowNode windowNode = structure.getWindowNodeAt(i);
192             dump(builder, windowNode.getRootViewNode(), " ", 0);
193         }
194         return builder.append(']').toString();
195     }
196 
197     @NonNull
toString(@onNull AssistStructure structure)198     public static String toString(@NonNull AssistStructure structure) {
199         return toString(structure, new StringBuilder());
200     }
201 
202     @Nullable
toString(@ullable AutofillValue value)203     public static String toString(@Nullable AutofillValue value) {
204         if (value == null) return null;
205         if (value.isText()) {
206             // We don't care about PII...
207             final CharSequence text = value.getTextValue();
208             return text == null ? null : text.toString();
209         }
210         return value.toString();
211     }
212 
213     /**
214      * Dump the assist structure on logcat.
215      */
dumpStructure(String message, AssistStructure structure)216     public static void dumpStructure(String message, AssistStructure structure) {
217         Log.i(TAG, toString(structure, new StringBuilder(message)));
218     }
219 
220     /**
221      * Dump the contexts on logcat.
222      */
dumpStructure(String message, List<FillContext> contexts)223     public static void dumpStructure(String message, List<FillContext> contexts) {
224         for (FillContext context : contexts) {
225             dumpStructure(message, context.getStructure());
226         }
227     }
228 
229     /**
230      * Dumps the state of the autofill service on logcat.
231      */
dumpAutofillService(@onNull String tag)232     public static void dumpAutofillService(@NonNull String tag) {
233         final String autofillDump = runShellCommand("dumpsys autofill");
234         Log.i(tag, "dumpsys autofill\n\n" + autofillDump);
235         final String myServiceDump = runShellCommand("dumpsys activity service %s",
236                 InstrumentedAutoFillService.SERVICE_NAME);
237         Log.i(tag, "my service dump: \n" + myServiceDump);
238     }
239 
240     /**
241      * Dumps the state of {@link android.service.autofill.InlineSuggestionRenderService}, and assert
242      * that it says the number of active inline suggestion views is the given number.
243      *
244      * <p>Note that ideally we should have a test api to fetch the number and verify against it.
245      * But at the time this test is added for Android 11, we have passed the deadline for adding
246      * the new test api, hence this approach.
247      */
assertActiveViewCountFromInlineSuggestionRenderService(int count)248     public static void assertActiveViewCountFromInlineSuggestionRenderService(int count) {
249         String response = runShellCommand(
250                 "dumpsys activity service .InlineSuggestionRenderService");
251         Log.d(TAG, "InlineSuggestionRenderService dump: " + response);
252         Pattern pattern = Pattern.compile(".*mActiveInlineSuggestions: " + count + ".*");
253         assertWithMessage("Expecting view count " + count
254                 + ", but seeing different count from service dumpsys " + response).that(
255                 pattern.matcher(response).find()).isTrue();
256     }
257 
258     /**
259      * Sets whether the user completed the initial setup.
260      */
setUserComplete(Context context, boolean complete)261     public static void setUserComplete(Context context, boolean complete) {
262         SettingsUtils.syncSet(context, USER_SETUP_COMPLETE, complete ? "1" : null);
263     }
264 
dump(@onNull StringBuilder builder, @NonNull ViewNode node, @NonNull String prefix, int childId)265     private static void dump(@NonNull StringBuilder builder, @NonNull ViewNode node,
266             @NonNull String prefix, int childId) {
267         final int childrenSize = node.getChildCount();
268         builder.append("\n").append(prefix)
269             .append("child #").append(childId).append(':');
270         append(builder, "afId", node.getAutofillId());
271         append(builder, "afType", node.getAutofillType());
272         append(builder, "afValue", toString(node.getAutofillValue()));
273         append(builder, "resId", node.getIdEntry());
274         append(builder, "class", node.getClassName());
275         append(builder, "text", node.getText());
276         append(builder, "webDomain", node.getWebDomain());
277         append(builder, "checked", node.isChecked());
278         append(builder, "focused", node.isFocused());
279         final HtmlInfo htmlInfo = node.getHtmlInfo();
280         if (htmlInfo != null) {
281             builder.append(", HtmlInfo[tag=").append(htmlInfo.getTag())
282                 .append(", attrs: ").append(htmlInfo.getAttributes()).append(']');
283         }
284         if (childrenSize > 0) {
285             append(builder, "#children", childrenSize).append("\n").append(prefix);
286             prefix += " ";
287             if (childrenSize > 0) {
288                 for (int i = 0; i < childrenSize; i++) {
289                     dump(builder, node.getChildAt(i), prefix, i);
290                 }
291             }
292         }
293     }
294 
295     /**
296      * Appends a field value to a {@link StringBuilder} when it's not {@code null}.
297      */
298     @NonNull
append(@onNull StringBuilder builder, @NonNull String field, @Nullable Object value)299     public static StringBuilder append(@NonNull StringBuilder builder, @NonNull String field,
300             @Nullable Object value) {
301         if (value == null) return builder;
302 
303         if ((value instanceof Boolean) && ((Boolean) value)) {
304             return builder.append(", ").append(field);
305         }
306 
307         if (value instanceof Integer && ((Integer) value) == 0
308                 || value instanceof CharSequence && TextUtils.isEmpty((CharSequence) value)) {
309             return builder;
310         }
311 
312         return builder.append(", ").append(field).append('=').append(value);
313     }
314 
315     /**
316      * Appends a field value to a {@link StringBuilder} when it's {@code true}.
317      */
318     @NonNull
append(@onNull StringBuilder builder, @NonNull String field, boolean value)319     public static StringBuilder append(@NonNull StringBuilder builder, @NonNull String field,
320             boolean value) {
321         if (value) {
322             builder.append(", ").append(field);
323         }
324         return builder;
325     }
326 
327     /**
328      * Gets a node if it matches the filter criteria for the given id.
329      */
findNodeByFilter(@onNull AssistStructure structure, @NonNull Object id, @NonNull NodeFilter<ViewNode> filter)330     public static ViewNode findNodeByFilter(@NonNull AssistStructure structure, @NonNull Object id,
331             @NonNull NodeFilter<ViewNode> filter) {
332         Log.v(TAG, "Parsing request for activity " + structure.getActivityComponent());
333         final int nodes = structure.getWindowNodeCount();
334         for (int i = 0; i < nodes; i++) {
335             final WindowNode windowNode = structure.getWindowNodeAt(i);
336             final ViewNode rootNode = windowNode.getRootViewNode();
337             final ViewNode node = findNodeByFilter(rootNode, id, filter);
338             if (node != null) {
339                 return node;
340             }
341         }
342         return null;
343     }
344 
345     /**
346      * Gets a node if it matches the filter criteria for the given id.
347      */
findNodeByFilter(@onNull List<FillContext> contexts, @NonNull Object id, @NonNull NodeFilter<ViewNode> filter)348     public static ViewNode findNodeByFilter(@NonNull List<FillContext> contexts, @NonNull Object id,
349             @NonNull NodeFilter<ViewNode> filter) {
350         for (FillContext context : contexts) {
351             ViewNode node = findNodeByFilter(context.getStructure(), id, filter);
352             if (node != null) {
353                 return node;
354             }
355         }
356         return null;
357     }
358 
359     /**
360      * Gets a node if it matches the filter criteria for the given id.
361      */
findNodeByFilter(@onNull ViewNode node, @NonNull Object id, @NonNull NodeFilter<ViewNode> filter)362     public static ViewNode findNodeByFilter(@NonNull ViewNode node, @NonNull Object id,
363             @NonNull NodeFilter<ViewNode> filter) {
364         if (filter.matches(node, id)) {
365             return node;
366         }
367         final int childrenSize = node.getChildCount();
368         if (childrenSize > 0) {
369             for (int i = 0; i < childrenSize; i++) {
370                 final ViewNode found = findNodeByFilter(node.getChildAt(i), id, filter);
371                 if (found != null) {
372                     return found;
373                 }
374             }
375         }
376         return null;
377     }
378 
379     /**
380      * Gets a node given its Android resource id, or {@code null} if not found.
381      */
findNodeByResourceId(AssistStructure structure, String resourceId)382     public static ViewNode findNodeByResourceId(AssistStructure structure, String resourceId) {
383         return findNodeByFilter(structure, resourceId, RESOURCE_ID_FILTER);
384     }
385 
386     /**
387      * Gets a node given its Android resource id, or {@code null} if not found.
388      */
findNodeByResourceId(List<FillContext> contexts, String resourceId)389     public static ViewNode findNodeByResourceId(List<FillContext> contexts, String resourceId) {
390         return findNodeByFilter(contexts, resourceId, RESOURCE_ID_FILTER);
391     }
392 
393     /**
394      * Gets a node given its Android resource id, or {@code null} if not found.
395      */
findNodeByResourceId(ViewNode node, String resourceId)396     public static ViewNode findNodeByResourceId(ViewNode node, String resourceId) {
397         return findNodeByFilter(node, resourceId, RESOURCE_ID_FILTER);
398     }
399 
400     /**
401      * Gets a node given the name of its HTML INPUT tag, or {@code null} if not found.
402      */
findNodeByHtmlName(AssistStructure structure, String htmlName)403     public static ViewNode findNodeByHtmlName(AssistStructure structure, String htmlName) {
404         return findNodeByFilter(structure, htmlName, HTML_NAME_FILTER);
405     }
406 
407     /**
408      * Gets a node given the name of its HTML INPUT tag, or {@code null} if not found.
409      */
findNodeByHtmlName(List<FillContext> contexts, String htmlName)410     public static ViewNode findNodeByHtmlName(List<FillContext> contexts, String htmlName) {
411         return findNodeByFilter(contexts, htmlName, HTML_NAME_FILTER);
412     }
413 
414     /**
415      * Gets a node given the name of its HTML INPUT tag, or {@code null} if not found.
416      */
findNodeByHtmlName(ViewNode node, String htmlName)417     public static ViewNode findNodeByHtmlName(ViewNode node, String htmlName) {
418         return findNodeByFilter(node, htmlName, HTML_NAME_FILTER);
419     }
420 
421     /**
422      * Gets a node given the value of its (single) autofill hint property, or {@code null} if not
423      * found.
424      */
findNodeByAutofillHint(ViewNode node, String hint)425     public static ViewNode findNodeByAutofillHint(ViewNode node, String hint) {
426         return findNodeByFilter(node, hint, AUTOFILL_HINT_FILTER);
427     }
428 
429     /**
430      * Gets a node given the name of its HTML INPUT tag or Android resoirce id, or {@code null} if
431      * not found.
432      */
findNodeByHtmlNameOrResourceId(List<FillContext> contexts, String id)433     public static ViewNode findNodeByHtmlNameOrResourceId(List<FillContext> contexts, String id) {
434         return findNodeByFilter(contexts, id, HTML_NAME_OR_RESOURCE_ID_FILTER);
435     }
436 
437     /**
438      * Gets a node given its Android resource id.
439      */
440     @NonNull
findAutofillIdByResourceId(@onNull FillContext context, @NonNull String resourceId)441     public static AutofillId findAutofillIdByResourceId(@NonNull FillContext context,
442             @NonNull String resourceId) {
443         final ViewNode node = findNodeByFilter(context.getStructure(), resourceId,
444                 RESOURCE_ID_FILTER);
445         assertWithMessage("No node for resourceId %s", resourceId).that(node).isNotNull();
446         return node.getAutofillId();
447     }
448 
449     /**
450      * Gets the {@code name} attribute of a node representing an HTML input tag.
451      */
452     @Nullable
getHtmlName(@onNull ViewNode node)453     public static String getHtmlName(@NonNull ViewNode node) {
454         final HtmlInfo htmlInfo = node.getHtmlInfo();
455         if (htmlInfo == null) {
456             return null;
457         }
458         final String tag = htmlInfo.getTag();
459         if (!"input".equals(tag)) {
460             Log.w(TAG, "getHtmlName(): invalid tag (" + tag + ") on " + htmlInfo);
461             return null;
462         }
463         for (Pair<String, String> attr : htmlInfo.getAttributes()) {
464             if ("name".equals(attr.first)) {
465                 return attr.second;
466             }
467         }
468         Log.w(TAG, "getHtmlName(): no 'name' attribute on " + htmlInfo);
469         return null;
470     }
471 
472     /**
473      * Gets a node given its expected text, or {@code null} if not found.
474      */
findNodeByText(AssistStructure structure, String text)475     public static ViewNode findNodeByText(AssistStructure structure, String text) {
476         return findNodeByFilter(structure, text, TEXT_FILTER);
477     }
478 
479     /**
480      * Gets a node given its expected text, or {@code null} if not found.
481      */
findNodeByText(ViewNode node, String text)482     public static ViewNode findNodeByText(ViewNode node, String text) {
483         return findNodeByFilter(node, text, TEXT_FILTER);
484     }
485 
486     /**
487      * Gets a view that contains the an autofill hint, or {@code null} if not found.
488      */
findViewByAutofillHint(Activity activity, String hint)489     public static View findViewByAutofillHint(Activity activity, String hint) {
490         final View rootView = activity.getWindow().getDecorView().getRootView();
491         return findViewByAutofillHint(rootView, hint);
492     }
493 
494     /**
495      * Gets a view (or a descendant of it) that contains the an autofill hint, or {@code null} if
496      * not found.
497      */
findViewByAutofillHint(View view, String hint)498     public static View findViewByAutofillHint(View view, String hint) {
499         if (AUTOFILL_HINT_VIEW_FILTER.matches(view, hint)) return view;
500         if ((view instanceof ViewGroup)) {
501             final ViewGroup group = (ViewGroup) view;
502             for (int i = 0; i < group.getChildCount(); i++) {
503                 final View child = findViewByAutofillHint(group.getChildAt(i), hint);
504                 if (child != null) return child;
505             }
506         }
507         return null;
508     }
509 
510     /**
511      * Asserts a text-based node is sanitized.
512      */
assertTextIsSanitized(ViewNode node)513     public static void assertTextIsSanitized(ViewNode node) {
514         final CharSequence text = node.getText();
515         final String resourceId = node.getIdEntry();
516         if (!TextUtils.isEmpty(text)) {
517             throw new AssertionError("text on sanitized field " + resourceId + ": " + text);
518         }
519 
520         assertNotFromResources(node);
521         assertNodeHasNoAutofillValue(node);
522     }
523 
assertNotFromResources(ViewNode node)524     private static void assertNotFromResources(ViewNode node) {
525         assertThat(node.getTextIdEntry()).isNull();
526     }
527 
assertNodeHasNoAutofillValue(ViewNode node)528     public static void assertNodeHasNoAutofillValue(ViewNode node) {
529         final AutofillValue value = node.getAutofillValue();
530         if (value != null) {
531             final String text = value.isText() ? value.getTextValue().toString() : "N/A";
532             throw new AssertionError("node has value: " + value + " text=" + text);
533         }
534     }
535 
536     /**
537      * Asserts the contents of a text-based node that is also auto-fillable.
538      */
assertTextOnly(ViewNode node, String expectedValue)539     public static void assertTextOnly(ViewNode node, String expectedValue) {
540         assertText(node, expectedValue, false);
541         assertNotFromResources(node);
542     }
543 
544     /**
545      * Asserts the contents of a text-based node that is also auto-fillable.
546      */
assertTextOnly(AssistStructure structure, String resourceId, String expectedValue)547     public static void assertTextOnly(AssistStructure structure, String resourceId,
548             String expectedValue) {
549         final ViewNode node = findNodeByResourceId(structure, resourceId);
550         assertText(node, expectedValue, false);
551         assertNotFromResources(node);
552     }
553 
554     /**
555      * Asserts the contents of a text-based node that is also auto-fillable.
556      */
assertTextAndValue(ViewNode node, String expectedValue)557     public static void assertTextAndValue(ViewNode node, String expectedValue) {
558         assertText(node, expectedValue, true);
559         assertNotFromResources(node);
560     }
561 
562     /**
563      * Asserts a text-based node exists and verify its values.
564      */
assertTextAndValue(AssistStructure structure, String resourceId, String expectedValue)565     public static ViewNode assertTextAndValue(AssistStructure structure, String resourceId,
566             String expectedValue) {
567         final ViewNode node = findNodeByResourceId(structure, resourceId);
568         assertTextAndValue(node, expectedValue);
569         return node;
570     }
571 
572     /**
573      * Asserts a text-based node exists and is sanitized.
574      */
assertValue(AssistStructure structure, String resourceId, String expectedValue)575     public static ViewNode assertValue(AssistStructure structure, String resourceId,
576             String expectedValue) {
577         final ViewNode node = findNodeByResourceId(structure, resourceId);
578         assertTextValue(node, expectedValue);
579         return node;
580     }
581 
582     /**
583      * Asserts the values of a text-based node whose string come from resoruces.
584      */
assertTextFromResources(AssistStructure structure, String resourceId, String expectedValue, boolean isAutofillable, String expectedTextIdEntry)585     public static ViewNode assertTextFromResources(AssistStructure structure, String resourceId,
586             String expectedValue, boolean isAutofillable, String expectedTextIdEntry) {
587         final ViewNode node = findNodeByResourceId(structure, resourceId);
588         assertText(node, expectedValue, isAutofillable);
589         assertThat(node.getTextIdEntry()).isEqualTo(expectedTextIdEntry);
590         return node;
591     }
592 
assertHintFromResources(AssistStructure structure, String resourceId, String expectedValue, String expectedHintIdEntry)593     public static ViewNode assertHintFromResources(AssistStructure structure, String resourceId,
594             String expectedValue, String expectedHintIdEntry) {
595         final ViewNode node = findNodeByResourceId(structure, resourceId);
596         assertThat(node.getHint()).isEqualTo(expectedValue);
597         assertThat(node.getHintIdEntry()).isEqualTo(expectedHintIdEntry);
598         return node;
599     }
600 
assertText(ViewNode node, String expectedValue, boolean isAutofillable)601     private static void assertText(ViewNode node, String expectedValue, boolean isAutofillable) {
602         assertWithMessage("wrong text on %s", node.getAutofillId()).that(node.getText().toString())
603                 .isEqualTo(expectedValue);
604         final AutofillValue value = node.getAutofillValue();
605         final AutofillId id = node.getAutofillId();
606         if (isAutofillable) {
607             assertWithMessage("null auto-fill value on %s", id).that(value).isNotNull();
608             assertWithMessage("wrong auto-fill value on %s", id)
609                     .that(value.getTextValue().toString()).isEqualTo(expectedValue);
610         } else {
611             assertWithMessage("node %s should not have AutofillValue", id).that(value).isNull();
612         }
613     }
614 
615     /**
616      * Asserts the auto-fill value of a text-based node.
617      */
assertTextValue(ViewNode node, String expectedText)618     public static ViewNode assertTextValue(ViewNode node, String expectedText) {
619         final AutofillValue value = node.getAutofillValue();
620         final AutofillId id = node.getAutofillId();
621         assertWithMessage("null autofill value on %s", id).that(value).isNotNull();
622         assertWithMessage("wrong autofill type on %s", id).that(value.isText()).isTrue();
623         assertWithMessage("wrong autofill value on %s", id).that(value.getTextValue().toString())
624                 .isEqualTo(expectedText);
625         return node;
626     }
627 
628     /**
629      * Asserts the auto-fill value of a list-based node.
630      */
assertListValue(ViewNode node, int expectedIndex)631     public static ViewNode assertListValue(ViewNode node, int expectedIndex) {
632         final AutofillValue value = node.getAutofillValue();
633         final AutofillId id = node.getAutofillId();
634         assertWithMessage("null autofill value on %s", id).that(value).isNotNull();
635         assertWithMessage("wrong autofill type on %s", id).that(value.isList()).isTrue();
636         assertWithMessage("wrong autofill value on %s", id).that(value.getListValue())
637                 .isEqualTo(expectedIndex);
638         return node;
639     }
640 
641     /**
642      * Asserts the auto-fill value of a toggle-based node.
643      */
assertToggleValue(ViewNode node, boolean expectedToggle)644     public static void assertToggleValue(ViewNode node, boolean expectedToggle) {
645         final AutofillValue value = node.getAutofillValue();
646         final AutofillId id = node.getAutofillId();
647         assertWithMessage("null autofill value on %s", id).that(value).isNotNull();
648         assertWithMessage("wrong autofill type on %s", id).that(value.isToggle()).isTrue();
649         assertWithMessage("wrong autofill value on %s", id).that(value.getToggleValue())
650                 .isEqualTo(expectedToggle);
651     }
652 
653     /**
654      * Asserts the auto-fill value of a date-based node.
655      */
assertDateValue(Object object, AutofillValue value, int year, int month, int day)656     public static void assertDateValue(Object object, AutofillValue value, int year, int month,
657             int day) {
658         assertWithMessage("null autofill value on %s", object).that(value).isNotNull();
659         assertWithMessage("wrong autofill type on %s", object).that(value.isDate()).isTrue();
660 
661         final Calendar cal = Calendar.getInstance();
662         cal.setTimeInMillis(value.getDateValue());
663 
664         assertWithMessage("Wrong year on AutofillValue %s", value)
665             .that(cal.get(Calendar.YEAR)).isEqualTo(year);
666         assertWithMessage("Wrong month on AutofillValue %s", value)
667             .that(cal.get(Calendar.MONTH)).isEqualTo(month);
668         assertWithMessage("Wrong day on AutofillValue %s", value)
669              .that(cal.get(Calendar.DAY_OF_MONTH)).isEqualTo(day);
670     }
671 
672     /**
673      * Asserts the auto-fill value of a date-based node.
674      */
assertDateValue(ViewNode node, int year, int month, int day)675     public static void assertDateValue(ViewNode node, int year, int month, int day) {
676         assertDateValue(node, node.getAutofillValue(), year, month, day);
677     }
678 
679     /**
680      * Asserts the auto-fill value of a date-based view.
681      */
assertDateValue(View view, int year, int month, int day)682     public static void assertDateValue(View view, int year, int month, int day) {
683         assertDateValue(view, view.getAutofillValue(), year, month, day);
684     }
685 
686     /**
687      * Asserts the auto-fill value of a time-based node.
688      */
assertTimeValue(Object object, AutofillValue value, int hour, int minute)689     private static void assertTimeValue(Object object, AutofillValue value, int hour, int minute) {
690         assertWithMessage("null autofill value on %s", object).that(value).isNotNull();
691         assertWithMessage("wrong autofill type on %s", object).that(value.isDate()).isTrue();
692 
693         final Calendar cal = Calendar.getInstance();
694         cal.setTimeInMillis(value.getDateValue());
695 
696         assertWithMessage("Wrong hour on AutofillValue %s", value)
697             .that(cal.get(Calendar.HOUR_OF_DAY)).isEqualTo(hour);
698         assertWithMessage("Wrong minute on AutofillValue %s", value)
699             .that(cal.get(Calendar.MINUTE)).isEqualTo(minute);
700     }
701 
702     /**
703      * Asserts the auto-fill value of a time-based node.
704      */
assertTimeValue(ViewNode node, int hour, int minute)705     public static void assertTimeValue(ViewNode node, int hour, int minute) {
706         assertTimeValue(node, node.getAutofillValue(), hour, minute);
707     }
708 
709     /**
710      * Asserts the auto-fill value of a time-based view.
711      */
assertTimeValue(View view, int hour, int minute)712     public static void assertTimeValue(View view, int hour, int minute) {
713         assertTimeValue(view, view.getAutofillValue(), hour, minute);
714     }
715 
716     /**
717      * Asserts a text-based node exists and is sanitized.
718      */
assertTextIsSanitized(AssistStructure structure, String resourceId)719     public static ViewNode assertTextIsSanitized(AssistStructure structure, String resourceId) {
720         final ViewNode node = findNodeByResourceId(structure, resourceId);
721         assertWithMessage("no ViewNode with id %s", resourceId).that(node).isNotNull();
722         assertTextIsSanitized(node);
723         return node;
724     }
725 
726     /**
727      * Asserts a list-based node exists and is sanitized.
728      */
assertListValueIsSanitized(AssistStructure structure, String resourceId)729     public static void assertListValueIsSanitized(AssistStructure structure, String resourceId) {
730         final ViewNode node = findNodeByResourceId(structure, resourceId);
731         assertWithMessage("no ViewNode with id %s", resourceId).that(node).isNotNull();
732         assertTextIsSanitized(node);
733     }
734 
735     /**
736      * Asserts a toggle node exists and is sanitized.
737      */
assertToggleIsSanitized(AssistStructure structure, String resourceId)738     public static void assertToggleIsSanitized(AssistStructure structure, String resourceId) {
739         final ViewNode node = findNodeByResourceId(structure, resourceId);
740         assertNodeHasNoAutofillValue(node);
741         assertWithMessage("ViewNode %s should not be checked", resourceId).that(node.isChecked())
742                 .isFalse();
743     }
744 
745     /**
746      * Asserts a node exists and has the {@code expected} number of children.
747      */
assertNumberOfChildren(AssistStructure structure, String resourceId, int expected)748     public static void assertNumberOfChildren(AssistStructure structure, String resourceId,
749             int expected) {
750         final ViewNode node = findNodeByResourceId(structure, resourceId);
751         final int actual = node.getChildCount();
752         if (actual != expected) {
753             dumpStructure("assertNumberOfChildren()", structure);
754             throw new AssertionError("assertNumberOfChildren() for " + resourceId
755                     + " failed: expected " + expected + ", got " + actual);
756         }
757     }
758 
759     /**
760      * Asserts the number of children in the Assist structure.
761      */
assertNumberOfChildrenWithWindowTitle(AssistStructure structure, int expected, CharSequence windowTitle)762     public static void assertNumberOfChildrenWithWindowTitle(AssistStructure structure,
763             int expected, CharSequence windowTitle) {
764         final int actual = getNumberNodes(structure, windowTitle);
765         if (actual != expected) {
766             dumpStructure("assertNumberOfChildren()", structure);
767             throw new AssertionError("assertNumberOfChildren() for structure failed: expected "
768                     + expected + ", got " + actual);
769         }
770     }
771 
772     /**
773      * Gets the total number of nodes in an structure.
774      */
getNumberNodes(AssistStructure structure, CharSequence windowTitle)775     public static int getNumberNodes(AssistStructure structure,
776             CharSequence windowTitle) {
777         int count = 0;
778         final int nodes = structure.getWindowNodeCount();
779         for (int i = 0; i < nodes; i++) {
780             final WindowNode windowNode = structure.getWindowNodeAt(i);
781             if (windowNode.getTitle().equals(windowTitle)) {
782                 final ViewNode rootNode = windowNode.getRootViewNode();
783                 count += getNumberNodes(rootNode);
784             }
785         }
786         return count;
787     }
788 
789     /**
790      * Gets the activity title.
791      */
getActivityTitle(Instrumentation instrumentation, Activity activity)792     public static CharSequence getActivityTitle(Instrumentation instrumentation,
793             Activity activity) {
794         final StringBuilder titleBuilder = new StringBuilder();
795         instrumentation.runOnMainSync(() -> titleBuilder.append(activity.getTitle()));
796         return titleBuilder;
797     }
798 
799     /**
800      * Gets the total number of nodes in an node, including all descendants and the node itself.
801      */
getNumberNodes(ViewNode node)802     public static int getNumberNodes(ViewNode node) {
803         int count = 1;
804         final int childrenSize = node.getChildCount();
805         if (childrenSize > 0) {
806             for (int i = 0; i < childrenSize; i++) {
807                 count += getNumberNodes(node.getChildAt(i));
808             }
809         }
810         return count;
811     }
812 
813     /**
814      * Creates an array of {@link AutofillId} mapped from the {@code structure} nodes with the given
815      * {@code resourceIds}.
816      */
getAutofillIds(Function<String, ViewNode> nodeResolver, String[] resourceIds)817     public static AutofillId[] getAutofillIds(Function<String, ViewNode> nodeResolver,
818             String[] resourceIds) {
819         if (resourceIds == null) return null;
820 
821         final AutofillId[] requiredIds = new AutofillId[resourceIds.length];
822         for (int i = 0; i < resourceIds.length; i++) {
823             final String resourceId = resourceIds[i];
824             final ViewNode node = nodeResolver.apply(resourceId);
825             if (node == null) {
826                 throw new AssertionError("No node with resourceId " + resourceId);
827             }
828             requiredIds[i] = node.getAutofillId();
829 
830         }
831         return requiredIds;
832     }
833 
834     /**
835      * Get an {@link AutofillId} mapped from the {@code structure} node with the given
836      * {@code resourceId}.
837      */
getAutofillId(Function<String, ViewNode> nodeResolver, String resourceId)838     public static AutofillId getAutofillId(Function<String, ViewNode> nodeResolver,
839             String resourceId) {
840         if (resourceId == null) return null;
841 
842         final ViewNode node = nodeResolver.apply(resourceId);
843         if (node == null) {
844             throw new AssertionError("No node with resourceId " + resourceId);
845         }
846         return node.getAutofillId();
847     }
848 
849     /**
850      * Prevents the screen to rotate by itself
851      */
disableAutoRotation(UiBot uiBot)852     public static void disableAutoRotation(UiBot uiBot) throws Exception {
853         runShellCommand(ACCELLEROMETER_CHANGE, 0);
854         uiBot.setScreenOrientation(PORTRAIT);
855     }
856 
857     /**
858      * Allows the screen to rotate by itself
859      */
allowAutoRotation()860     public static void allowAutoRotation() {
861         runShellCommand(ACCELLEROMETER_CHANGE, 1);
862     }
863 
864     /**
865      * Gets the maximum number of partitions per session.
866      */
getMaxPartitions()867     public static int getMaxPartitions() {
868         return Integer.parseInt(runShellCommand("cmd autofill get max_partitions"));
869     }
870 
871     /**
872      * Sets the maximum number of partitions per session.
873      */
setMaxPartitions(int value)874     public static void setMaxPartitions(int value) throws Exception {
875         runShellCommand("cmd autofill set max_partitions %d", value);
876         SETTINGS_BASED_SHELL_CMD_TIMEOUT.run("get max_partitions", () -> {
877             return getMaxPartitions() == value ? Boolean.TRUE : null;
878         });
879     }
880 
881     /**
882      * Gets the maximum number of visible datasets.
883      */
getMaxVisibleDatasets()884     public static int getMaxVisibleDatasets() {
885         return Integer.parseInt(runShellCommand("cmd autofill get max_visible_datasets"));
886     }
887 
888     /**
889      * Sets the maximum number of visible datasets.
890      */
setMaxVisibleDatasets(int value)891     public static void setMaxVisibleDatasets(int value) throws Exception {
892         runShellCommand("cmd autofill set max_visible_datasets %d", value);
893         SETTINGS_BASED_SHELL_CMD_TIMEOUT.run("get max_visible_datasets", () -> {
894             return getMaxVisibleDatasets() == value ? Boolean.TRUE : null;
895         });
896     }
897 
898     /**
899      * Checks if autofill window is fullscreen, see com.android.server.autofill.ui.FillUi.
900      */
isAutofillWindowFullScreen(Context context)901     public static boolean isAutofillWindowFullScreen(Context context) {
902         return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK);
903     }
904 
905     /**
906      * Checks if screen orientation can be changed.
907      */
isRotationSupported(Context context)908     public static boolean isRotationSupported(Context context) {
909         final PackageManager packageManager = context.getPackageManager();
910         if (packageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
911             Log.v(TAG, "isRotationSupported(): is auto");
912             return false;
913         }
914         if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK)) {
915             Log.v(TAG, "isRotationSupported(): has leanback feature");
916             return false;
917         }
918         if (packageManager.hasSystemFeature(PackageManager.FEATURE_PC)) {
919             Log.v(TAG, "isRotationSupported(): is PC");
920             return false;
921         }
922         if (!packageManager.hasSystemFeature(PackageManager.FEATURE_SCREEN_LANDSCAPE)
923                 || !packageManager.hasSystemFeature(PackageManager.FEATURE_SCREEN_PORTRAIT)) {
924             Log.v(TAG, "isRotationSupported(): no screen orientation feature");
925             return false;
926         }
927         return true;
928     }
929 
getBoolean(Context context, String id)930     private static boolean getBoolean(Context context, String id) {
931         final Resources resources = context.getResources();
932         final int booleanId = resources.getIdentifier(id, "bool", "android");
933         return resources.getBoolean(booleanId);
934     }
935 
936     /**
937      * Uses Shell command to get the Autofill logging level.
938      */
getLoggingLevel()939     public static String getLoggingLevel() {
940         return runShellCommand("cmd autofill get log_level");
941     }
942 
943     /**
944      * Uses Shell command to set the Autofill logging level.
945      */
setLoggingLevel(String level)946     public static void setLoggingLevel(String level) {
947         runShellCommand("cmd autofill set log_level %s", level);
948     }
949 
950     /**
951      * Uses Settings to enable the given autofill service for the default user, and checks the
952      * value was properly check, throwing an exception if it was not.
953      */
enableAutofillService(@onNull Context context, @NonNull String serviceName)954     public static void enableAutofillService(@NonNull Context context,
955             @NonNull String serviceName) {
956         if (isAutofillServiceEnabled(serviceName)) return;
957 
958         // Sets the setting synchronously. Note that the config itself is sets synchronously but
959         // launch of the service is asynchronous after the config is updated.
960         SettingsUtils.syncSet(context, AUTOFILL_SERVICE, serviceName);
961 
962         // Waits until the service is actually enabled.
963         try {
964             Timeouts.CONNECTION_TIMEOUT.run("Enabling Autofill service", () -> {
965                 return isAutofillServiceEnabled(serviceName) ? serviceName : null;
966             });
967         } catch (Exception e) {
968             throw new AssertionError("Enabling Autofill service failed.");
969         }
970     }
971 
972     /**
973      * Uses Settings to disable the given autofill service for the default user, and waits until
974      * the setting is deleted.
975      */
disableAutofillService(@onNull Context context)976     public static void disableAutofillService(@NonNull Context context) {
977         final String currentService = SettingsUtils.get(AUTOFILL_SERVICE);
978         if (currentService == null) {
979             Log.v(TAG, "disableAutofillService(): already disabled");
980             return;
981         }
982         Log.v(TAG, "Disabling " + currentService);
983         SettingsUtils.syncDelete(context, AUTOFILL_SERVICE);
984     }
985 
986     /**
987      * Checks whether the given service is set as the autofill service for the default user.
988      */
isAutofillServiceEnabled(@onNull String serviceName)989     public static boolean isAutofillServiceEnabled(@NonNull String serviceName) {
990         final String actualName = getAutofillServiceName();
991         return serviceName.equals(actualName);
992     }
993 
994     /**
995      * Gets then name of the autofill service for the default user.
996      */
getAutofillServiceName()997     public static String getAutofillServiceName() {
998         return SettingsUtils.get(AUTOFILL_SERVICE);
999     }
1000 
1001     /**
1002      * Asserts whether the given service is enabled as the autofill service for the default user.
1003      */
assertAutofillServiceStatus(@onNull String serviceName, boolean enabled)1004     public static void assertAutofillServiceStatus(@NonNull String serviceName, boolean enabled) {
1005         final String actual = SettingsUtils.get(AUTOFILL_SERVICE);
1006         final String expected = enabled ? serviceName : null;
1007         assertWithMessage("Invalid value for secure setting %s", AUTOFILL_SERVICE)
1008                 .that(actual).isEqualTo(expected);
1009     }
1010 
1011     /**
1012      * Enables / disables the default augmented autofill service.
1013      */
setDefaultAugmentedAutofillServiceEnabled(boolean enabled)1014     public static void setDefaultAugmentedAutofillServiceEnabled(boolean enabled) {
1015         Log.d(TAG, "setDefaultAugmentedAutofillServiceEnabled(): " + enabled);
1016         runShellCommand("cmd autofill set default-augmented-service-enabled 0 %s",
1017                 Boolean.toString(enabled));
1018     }
1019 
1020     /**
1021      * Gets the instrumentation context.
1022      */
getContext()1023     public static Context getContext() {
1024         return InstrumentationRegistry.getInstrumentation().getContext();
1025     }
1026 
1027     /**
1028      * Asserts the node has an {@code HTMLInfo} property, with the given tag.
1029      */
assertHasHtmlTag(ViewNode node, String expectedTag)1030     public static HtmlInfo assertHasHtmlTag(ViewNode node, String expectedTag) {
1031         final HtmlInfo info = node.getHtmlInfo();
1032         assertWithMessage("node doesn't have htmlInfo").that(info).isNotNull();
1033         assertWithMessage("wrong tag").that(info.getTag()).isEqualTo(expectedTag);
1034         return info;
1035     }
1036 
1037     /**
1038      * Gets the value of an {@code HTMLInfo} attribute.
1039      */
1040     @Nullable
getAttributeValue(HtmlInfo info, String attribute)1041     public static String getAttributeValue(HtmlInfo info, String attribute) {
1042         for (Pair<String, String> pair : info.getAttributes()) {
1043             if (pair.first.equals(attribute)) {
1044                 return pair.second;
1045             }
1046         }
1047         return null;
1048     }
1049 
1050     /**
1051      * Asserts a {@code HTMLInfo} has an attribute with a given value.
1052      */
assertHasAttribute(HtmlInfo info, String attribute, String expectedValue)1053     public static void assertHasAttribute(HtmlInfo info, String attribute, String expectedValue) {
1054         final String actualValue = getAttributeValue(info, attribute);
1055         assertWithMessage("Attribute %s not found", attribute).that(actualValue).isNotNull();
1056         assertWithMessage("Wrong value for Attribute %s", attribute)
1057             .that(actualValue).isEqualTo(expectedValue);
1058     }
1059 
1060     /**
1061      * Finds a {@link WebView} node given its expected form name.
1062      */
findWebViewNodeByFormName(AssistStructure structure, String formName)1063     public static ViewNode findWebViewNodeByFormName(AssistStructure structure, String formName) {
1064         return findNodeByFilter(structure, formName, WEBVIEW_FORM_FILTER);
1065     }
1066 
assertClientState(Object container, Bundle clientState, String key, String value)1067     private static void assertClientState(Object container, Bundle clientState,
1068             String key, String value) {
1069         assertWithMessage("'%s' should have client state", container)
1070             .that(clientState).isNotNull();
1071         assertWithMessage("Wrong number of client state extras on '%s'", container)
1072             .that(clientState.keySet().size()).isEqualTo(1);
1073         assertWithMessage("Wrong value for client state key (%s) on '%s'", key, container)
1074             .that(clientState.getString(key)).isEqualTo(value);
1075     }
1076 
1077     /**
1078      * Asserts the content of a {@link FillEventHistory#getClientState()}.
1079      *
1080      * @param history event to be asserted
1081      * @param key the only key expected in the client state bundle
1082      * @param value the only value expected in the client state bundle
1083      */
1084     @SuppressWarnings("javadoc")
assertDeprecatedClientState(@onNull FillEventHistory history, @NonNull String key, @NonNull String value)1085     public static void assertDeprecatedClientState(@NonNull FillEventHistory history,
1086             @NonNull String key, @NonNull String value) {
1087         assertThat(history).isNotNull();
1088         @SuppressWarnings("deprecation")
1089         final Bundle clientState = history.getClientState();
1090         assertClientState(history, clientState, key, value);
1091     }
1092 
1093     /**
1094      * Asserts the {@link FillEventHistory#getClientState()} is not set.
1095      *
1096      * @param history event to be asserted
1097      */
1098     @SuppressWarnings("javadoc")
assertNoDeprecatedClientState(@onNull FillEventHistory history)1099     public static void assertNoDeprecatedClientState(@NonNull FillEventHistory history) {
1100         assertThat(history).isNotNull();
1101         @SuppressWarnings("deprecation")
1102         final Bundle clientState = history.getClientState();
1103         assertWithMessage("History '%s' should not have client state", history)
1104              .that(clientState).isNull();
1105     }
1106 
1107     /**
1108      * Asserts the content of a {@link android.service.autofill.FillEventHistory.Event}.
1109      *
1110      * @param event event to be asserted
1111      * @param eventType expected type
1112      * @param datasetId dataset set id expected in the event
1113      * @param key the only key expected in the client state bundle (or {@code null} if it shouldn't
1114      * have client state)
1115      * @param value the only value expected in the client state bundle (or {@code null} if it
1116      * shouldn't have client state)
1117      * @param fieldClassificationResults expected results when asserting field classification
1118      */
assertFillEvent(@onNull FillEventHistory.Event event, int eventType, @Nullable String datasetId, @Nullable String key, @Nullable String value, @Nullable FieldClassificationResult[] fieldClassificationResults)1119     private static void assertFillEvent(@NonNull FillEventHistory.Event event,
1120             int eventType, @Nullable String datasetId,
1121             @Nullable String key, @Nullable String value,
1122             @Nullable FieldClassificationResult[] fieldClassificationResults) {
1123         assertThat(event).isNotNull();
1124         assertWithMessage("Wrong type for %s", event).that(event.getType()).isEqualTo(eventType);
1125         if (datasetId == null) {
1126             assertWithMessage("Event %s should not have dataset id", event)
1127                 .that(event.getDatasetId()).isNull();
1128         } else {
1129             assertWithMessage("Wrong dataset id for %s", event)
1130                 .that(event.getDatasetId()).isEqualTo(datasetId);
1131         }
1132         final Bundle clientState = event.getClientState();
1133         if (key == null) {
1134             assertWithMessage("Event '%s' should not have client state", event)
1135                 .that(clientState).isNull();
1136         } else {
1137             assertClientState(event, clientState, key, value);
1138         }
1139         assertWithMessage("Event '%s' should not have selected datasets", event)
1140                 .that(event.getSelectedDatasetIds()).isEmpty();
1141         assertWithMessage("Event '%s' should not have ignored datasets", event)
1142                 .that(event.getIgnoredDatasetIds()).isEmpty();
1143         assertWithMessage("Event '%s' should not have changed fields", event)
1144                 .that(event.getChangedFields()).isEmpty();
1145         assertWithMessage("Event '%s' should not have manually-entered fields", event)
1146                 .that(event.getManuallyEnteredField()).isEmpty();
1147         final Map<AutofillId, FieldClassification> detectedFields = event.getFieldsClassification();
1148         if (fieldClassificationResults == null) {
1149             assertThat(detectedFields).isEmpty();
1150         } else {
1151             assertThat(detectedFields).hasSize(fieldClassificationResults.length);
1152             int i = 0;
1153             for (Entry<AutofillId, FieldClassification> entry : detectedFields.entrySet()) {
1154                 assertMatches(i, entry, fieldClassificationResults[i]);
1155                 i++;
1156             }
1157         }
1158     }
1159 
assertMatches(int i, Entry<AutofillId, FieldClassification> actualResult, FieldClassificationResult expectedResult)1160     private static void assertMatches(int i, Entry<AutofillId, FieldClassification> actualResult,
1161             FieldClassificationResult expectedResult) {
1162         assertWithMessage("Wrong field id at index %s", i).that(actualResult.getKey())
1163                 .isEqualTo(expectedResult.id);
1164         final List<Match> matches = actualResult.getValue().getMatches();
1165         assertWithMessage("Wrong number of matches: " + matches).that(matches.size())
1166                 .isEqualTo(expectedResult.categoryIds.length);
1167         for (int j = 0; j < matches.size(); j++) {
1168             final Match match = matches.get(j);
1169             assertWithMessage("Wrong categoryId at (%s, %s): %s", i, j, match)
1170                 .that(match.getCategoryId()).isEqualTo(expectedResult.categoryIds[j]);
1171             assertWithMessage("Wrong score at (%s, %s): %s", i, j, match)
1172                 .that(match.getScore()).isWithin(0.01f).of(expectedResult.scores[j]);
1173         }
1174     }
1175 
1176     /**
1177      * Asserts the content of a
1178      * {@link android.service.autofill.FillEventHistory.Event#TYPE_DATASET_SELECTED} event.
1179      *
1180      * @param event event to be asserted
1181      * @param datasetId dataset set id expected in the event
1182      */
assertFillEventForDatasetSelected(@onNull FillEventHistory.Event event, @Nullable String datasetId)1183     public static void assertFillEventForDatasetSelected(@NonNull FillEventHistory.Event event,
1184             @Nullable String datasetId) {
1185         assertFillEvent(event, TYPE_DATASET_SELECTED, datasetId, null, null, null);
1186     }
1187 
1188     /**
1189      * Asserts the content of a
1190      * {@link android.service.autofill.FillEventHistory.Event#TYPE_DATASET_SELECTED} event.
1191      *
1192      * @param event event to be asserted
1193      * @param datasetId dataset set id expected in the event
1194      * @param key the only key expected in the client state bundle
1195      * @param value the only value expected in the client state bundle
1196      */
assertFillEventForDatasetSelected(@onNull FillEventHistory.Event event, @Nullable String datasetId, @Nullable String key, @Nullable String value)1197     public static void assertFillEventForDatasetSelected(@NonNull FillEventHistory.Event event,
1198             @Nullable String datasetId, @Nullable String key, @Nullable String value) {
1199         assertFillEvent(event, TYPE_DATASET_SELECTED, datasetId, key, value, null);
1200     }
1201 
1202     /**
1203      * Asserts the content of a
1204      * {@link android.service.autofill.FillEventHistory.Event#TYPE_SAVE_SHOWN} event.
1205      *
1206      * @param event event to be asserted
1207      * @param datasetId dataset set id expected in the event
1208      * @param key the only key expected in the client state bundle
1209      * @param value the only value expected in the client state bundle
1210      */
assertFillEventForSaveShown(@onNull FillEventHistory.Event event, @Nullable String datasetId, @NonNull String key, @NonNull String value)1211     public static void assertFillEventForSaveShown(@NonNull FillEventHistory.Event event,
1212             @Nullable String datasetId, @NonNull String key, @NonNull String value) {
1213         assertFillEvent(event, TYPE_SAVE_SHOWN, datasetId, key, value, null);
1214     }
1215 
1216     /**
1217      * Asserts the content of a
1218      * {@link android.service.autofill.FillEventHistory.Event#TYPE_SAVE_SHOWN} event.
1219      *
1220      * @param event event to be asserted
1221      * @param datasetId dataset set id expected in the event
1222      */
assertFillEventForSaveShown(@onNull FillEventHistory.Event event, @Nullable String datasetId)1223     public static void assertFillEventForSaveShown(@NonNull FillEventHistory.Event event,
1224             @Nullable String datasetId) {
1225         assertFillEvent(event, TYPE_SAVE_SHOWN, datasetId, null, null, null);
1226     }
1227 
1228     /**
1229      * Asserts the content of a
1230      * {@link android.service.autofill.FillEventHistory.Event#TYPE_DATASETS_SHOWN} event.
1231      *
1232      * @param event event to be asserted
1233      * @param key the only key expected in the client state bundle
1234      * @param value the only value expected in the client state bundle
1235      */
assertFillEventForDatasetShown(@onNull FillEventHistory.Event event, @NonNull String key, @NonNull String value)1236     public static void assertFillEventForDatasetShown(@NonNull FillEventHistory.Event event,
1237             @NonNull String key, @NonNull String value) {
1238         assertFillEvent(event, TYPE_DATASETS_SHOWN, NULL_DATASET_ID, key, value, null);
1239     }
1240 
1241     /**
1242      * Asserts the content of a
1243      * {@link android.service.autofill.FillEventHistory.Event#TYPE_DATASETS_SHOWN} event.
1244      *
1245      * @param event event to be asserted
1246      */
assertFillEventForDatasetShown(@onNull FillEventHistory.Event event)1247     public static void assertFillEventForDatasetShown(@NonNull FillEventHistory.Event event) {
1248         assertFillEvent(event, TYPE_DATASETS_SHOWN, NULL_DATASET_ID, null, null, null);
1249     }
1250 
1251     /**
1252      * Asserts the content of a
1253      * {@link android.service.autofill.FillEventHistory.Event#TYPE_DATASET_AUTHENTICATION_SELECTED}
1254      * event.
1255      *
1256      * @param event event to be asserted
1257      * @param datasetId dataset set id expected in the event
1258      * @param key the only key expected in the client state bundle
1259      * @param value the only value expected in the client state bundle
1260      */
assertFillEventForDatasetAuthenticationSelected( @onNull FillEventHistory.Event event, @Nullable String datasetId, @NonNull String key, @NonNull String value)1261     public static void assertFillEventForDatasetAuthenticationSelected(
1262             @NonNull FillEventHistory.Event event,
1263             @Nullable String datasetId, @NonNull String key, @NonNull String value) {
1264         assertFillEvent(event, TYPE_DATASET_AUTHENTICATION_SELECTED, datasetId, key, value, null);
1265     }
1266 
1267     /**
1268      * Asserts the content of a
1269      * {@link android.service.autofill.FillEventHistory.Event#TYPE_AUTHENTICATION_SELECTED} event.
1270      *
1271      * @param event event to be asserted
1272      * @param datasetId dataset set id expected in the event
1273      * @param key the only key expected in the client state bundle
1274      * @param value the only value expected in the client state bundle
1275      */
assertFillEventForAuthenticationSelected( @onNull FillEventHistory.Event event, @Nullable String datasetId, @NonNull String key, @NonNull String value)1276     public static void assertFillEventForAuthenticationSelected(
1277             @NonNull FillEventHistory.Event event,
1278             @Nullable String datasetId, @NonNull String key, @NonNull String value) {
1279         assertFillEvent(event, TYPE_AUTHENTICATION_SELECTED, datasetId, key, value, null);
1280     }
1281 
assertFillEventForFieldsClassification(@onNull FillEventHistory.Event event, @NonNull AutofillId fieldId, @NonNull String categoryId, float score)1282     public static void assertFillEventForFieldsClassification(@NonNull FillEventHistory.Event event,
1283             @NonNull AutofillId fieldId, @NonNull String categoryId, float score) {
1284         assertFillEvent(event, TYPE_CONTEXT_COMMITTED, null, null, null,
1285                 new FieldClassificationResult[] {
1286                         new FieldClassificationResult(fieldId, categoryId, score)
1287                 });
1288     }
1289 
assertFillEventForFieldsClassification(@onNull FillEventHistory.Event event, @NonNull FieldClassificationResult[] results)1290     public static void assertFillEventForFieldsClassification(@NonNull FillEventHistory.Event event,
1291             @NonNull FieldClassificationResult[] results) {
1292         assertFillEvent(event, TYPE_CONTEXT_COMMITTED, null, null, null, results);
1293     }
1294 
assertFillEventForContextCommitted(@onNull FillEventHistory.Event event)1295     public static void assertFillEventForContextCommitted(@NonNull FillEventHistory.Event event) {
1296         assertFillEvent(event, TYPE_CONTEXT_COMMITTED, null, null, null, null);
1297     }
1298 
1299     @NonNull
getActivityName(List<FillContext> contexts)1300     public static String getActivityName(List<FillContext> contexts) {
1301         if (contexts == null) return "N/A (null contexts)";
1302 
1303         if (contexts.isEmpty()) return "N/A (empty contexts)";
1304 
1305         final AssistStructure structure = contexts.get(contexts.size() - 1).getStructure();
1306         if (structure == null) return "N/A (no AssistStructure)";
1307 
1308         final ComponentName componentName = structure.getActivityComponent();
1309         if (componentName == null) return "N/A (no component name)";
1310 
1311         return componentName.flattenToShortString();
1312     }
1313 
assertFloat(float actualValue, float expectedValue)1314     public static void assertFloat(float actualValue, float expectedValue) {
1315         assertThat(actualValue).isWithin(1.0e-10f).of(expectedValue);
1316     }
1317 
assertHasFlags(int actualFlags, int expectedFlags)1318     public static void assertHasFlags(int actualFlags, int expectedFlags) {
1319         assertWithMessage("Flags %s not in %s", expectedFlags, actualFlags)
1320                 .that(actualFlags & expectedFlags).isEqualTo(expectedFlags);
1321     }
1322 
callbackEventAsString(int event)1323     public static String callbackEventAsString(int event) {
1324         switch (event) {
1325             case AutofillCallback.EVENT_INPUT_HIDDEN:
1326                 return "HIDDEN";
1327             case AutofillCallback.EVENT_INPUT_SHOWN:
1328                 return "SHOWN";
1329             case AutofillCallback.EVENT_INPUT_UNAVAILABLE:
1330                 return "UNAVAILABLE";
1331             default:
1332                 return "UNKNOWN:" + event;
1333         }
1334     }
1335 
importantForAutofillAsString(int mode)1336     public static String importantForAutofillAsString(int mode) {
1337         switch (mode) {
1338             case View.IMPORTANT_FOR_AUTOFILL_AUTO:
1339                 return "IMPORTANT_FOR_AUTOFILL_AUTO";
1340             case View.IMPORTANT_FOR_AUTOFILL_YES:
1341                 return "IMPORTANT_FOR_AUTOFILL_YES";
1342             case View.IMPORTANT_FOR_AUTOFILL_YES_EXCLUDE_DESCENDANTS:
1343                 return "IMPORTANT_FOR_AUTOFILL_YES_EXCLUDE_DESCENDANTS";
1344             case View.IMPORTANT_FOR_AUTOFILL_NO:
1345                 return "IMPORTANT_FOR_AUTOFILL_NO";
1346             case View.IMPORTANT_FOR_AUTOFILL_NO_EXCLUDE_DESCENDANTS:
1347                 return "IMPORTANT_FOR_AUTOFILL_NO_EXCLUDE_DESCENDANTS";
1348             default:
1349                 return "UNKNOWN:" + mode;
1350         }
1351     }
1352 
hasHint(@ullable String[] hints, @Nullable Object expectedHint)1353     public static boolean hasHint(@Nullable String[] hints, @Nullable Object expectedHint) {
1354         if (hints == null || expectedHint == null) return false;
1355         for (String actualHint : hints) {
1356             if (expectedHint.equals(actualHint)) return true;
1357         }
1358         return false;
1359     }
1360 
newClientState(String key, String value)1361     public static Bundle newClientState(String key, String value) {
1362         final Bundle clientState = new Bundle();
1363         clientState.putString(key, value);
1364         return clientState;
1365     }
1366 
assertAuthenticationClientState(String where, Bundle data, String expectedKey, String expectedValue)1367     public static void assertAuthenticationClientState(String where, Bundle data,
1368             String expectedKey, String expectedValue) {
1369         assertWithMessage("no client state on %s", where).that(data).isNotNull();
1370         final String extraValue = data.getString(expectedKey);
1371         assertWithMessage("invalid value for %s on %s", expectedKey, where)
1372                 .that(extraValue).isEqualTo(expectedValue);
1373     }
1374 
1375     /**
1376      * Asserts that 2 bitmaps have are the same. If they aren't throws an exception and dump them
1377      * locally so their can be visually inspected.
1378      *
1379      * @param filename base name of the files generated in case of error
1380      * @param bitmap1 first bitmap to be compared
1381      * @param bitmap2 second bitmap to be compared
1382      */
1383     // TODO: move to common code
assertBitmapsAreSame(@onNull String filename, @Nullable Bitmap bitmap1, @Nullable Bitmap bitmap2)1384     public static void assertBitmapsAreSame(@NonNull String filename, @Nullable Bitmap bitmap1,
1385             @Nullable Bitmap bitmap2) throws IOException {
1386         assertWithMessage("1st bitmap is null").that(bitmap1).isNotNull();
1387         assertWithMessage("2nd bitmap is null").that(bitmap2).isNotNull();
1388         final boolean same = bitmap1.sameAs(bitmap2);
1389         if (same) {
1390             Log.v(TAG, "bitmap comparison passed for " + filename);
1391             return;
1392         }
1393 
1394         final File dir = getLocalDirectory();
1395         if (dir == null) {
1396             throw new AssertionError("bitmap comparison failed for " + filename
1397                     + ", and bitmaps could not be dumped on " + dir);
1398         }
1399         final File dump1 = dumpBitmap(bitmap1, dir, filename + "-1.png");
1400         final File dump2 = dumpBitmap(bitmap2, dir, filename + "-2.png");
1401         throw new AssertionError(
1402                 "bitmap comparison failed; check contents of " + dump1 + " and " + dump2);
1403     }
1404 
1405     @Nullable
getLocalDirectory()1406     private static File getLocalDirectory() {
1407         final File dir = new File(LOCAL_DIRECTORY);
1408         dir.mkdirs();
1409         if (!dir.exists()) {
1410             Log.e(TAG, "Could not create directory " + dir);
1411             return null;
1412         }
1413         return dir;
1414     }
1415 
1416     @Nullable
createFile(@onNull File dir, @NonNull String filename)1417     private static File createFile(@NonNull File dir, @NonNull String filename) throws IOException {
1418         final File file = new File(dir, filename);
1419         if (file.exists()) {
1420             Log.v(TAG, "Deleting file " + file);
1421             file.delete();
1422         }
1423         if (!file.createNewFile()) {
1424             Log.e(TAG, "Could not create file " + file);
1425             return null;
1426         }
1427         return file;
1428     }
1429 
1430     @Nullable
dumpBitmap(@onNull Bitmap bitmap, @NonNull File dir, @NonNull String filename)1431     private static File dumpBitmap(@NonNull Bitmap bitmap, @NonNull File dir,
1432             @NonNull String filename) throws IOException {
1433         final File file = createFile(dir, filename);
1434         if (file != null) {
1435             dumpBitmap(bitmap, file);
1436 
1437         }
1438         return file;
1439     }
1440 
1441     @Nullable
dumpBitmap(@onNull Bitmap bitmap, @NonNull File file)1442     public static File dumpBitmap(@NonNull Bitmap bitmap, @NonNull File file) {
1443         Log.i(TAG, "Dumping bitmap at " + file);
1444         BitmapUtils.saveBitmap(bitmap, file.getParent(), file.getName());
1445         return file;
1446     }
1447 
1448     /**
1449      * Creates a file in the device, using the name of the current test as a prefix.
1450      */
1451     @Nullable
createTestFile(@onNull String name)1452     public static File createTestFile(@NonNull String name) throws IOException {
1453         final File dir = getLocalDirectory();
1454         if (dir == null) return null;
1455 
1456         final String prefix = TestNameUtils.getCurrentTestName().replaceAll("\\.|\\(|\\/", "_")
1457                 .replaceAll("\\)", "");
1458         final String filename = prefix + "-" + name;
1459 
1460         return createFile(dir, filename);
1461     }
1462 
1463     /**
1464      * Offers an object to a queue or times out.
1465      *
1466      * @return {@code true} if the offer was accepted, {$code false} if it timed out or was
1467      * interrupted.
1468      */
offer(BlockingQueue<T> queue, T obj, long timeoutMs)1469     public static <T> boolean offer(BlockingQueue<T> queue, T obj, long timeoutMs) {
1470         boolean offered = false;
1471         try {
1472             offered = queue.offer(obj, timeoutMs, TimeUnit.MILLISECONDS);
1473         } catch (InterruptedException e) {
1474             Log.w(TAG, "interrupted offering", e);
1475             Thread.currentThread().interrupt();
1476         }
1477         if (!offered) {
1478             Log.e(TAG, "could not offer " + obj + " in " + timeoutMs + "ms");
1479         }
1480         return offered;
1481     }
1482 
1483     /**
1484      * Calls this method to assert given {@code string} is equal to {@link #LARGE_STRING}, as
1485      * comparing its value using standard assertions might ANR.
1486      */
assertEqualsToLargeString(@onNull String string)1487     public static void assertEqualsToLargeString(@NonNull String string) {
1488         assertThat(string).isNotNull();
1489         assertThat(string).hasLength(LARGE_STRING_SIZE);
1490         assertThat(string.charAt(0)).isEqualTo(LARGE_STRING_CHAR);
1491         assertThat(string.charAt(LARGE_STRING_SIZE - 1)).isEqualTo(LARGE_STRING_CHAR);
1492     }
1493 
1494     /**
1495      * Asserts that autofill is enabled in the context, retrying if necessariy.
1496      */
assertAutofillEnabled(@onNull Context context, boolean expected)1497     public static void assertAutofillEnabled(@NonNull Context context, boolean expected)
1498             throws Exception {
1499         assertAutofillEnabled(context.getSystemService(AutofillManager.class), expected);
1500     }
1501 
1502     /**
1503      * Asserts that autofill is enabled in the manager, retrying if necessariy.
1504      */
assertAutofillEnabled(@onNull AutofillManager afm, boolean expected)1505     public static void assertAutofillEnabled(@NonNull AutofillManager afm, boolean expected)
1506             throws Exception {
1507         Timeouts.IDLE_UNBIND_TIMEOUT.run("assertEnabled(" + expected + ")", () -> {
1508             final boolean actual = afm.isEnabled();
1509             Log.v(TAG, "assertEnabled(): expected=" + expected + ", actual=" + actual);
1510             return actual == expected ? "not_used" : null;
1511         });
1512     }
1513 
1514     /**
1515      * Asserts these autofill ids are the same, except for the session.
1516      */
assertEqualsIgnoreSession(@onNull AutofillId id1, @NonNull AutofillId id2)1517     public static void assertEqualsIgnoreSession(@NonNull AutofillId id1, @NonNull AutofillId id2) {
1518         assertWithMessage("id1 is null").that(id1).isNotNull();
1519         assertWithMessage("id2 is null").that(id2).isNotNull();
1520         assertWithMessage("%s is not equal to %s", id1, id2).that(id1.equalsIgnoreSession(id2))
1521                 .isTrue();
1522     }
1523 
1524     /**
1525      * Asserts {@link View#isAutofilled()} state of the given view, waiting if necessarity to avoid
1526      * race conditions.
1527      */
assertViewAutofillState(@onNull View view, boolean expected)1528     public static void assertViewAutofillState(@NonNull View view, boolean expected)
1529             throws Exception {
1530         Timeouts.FILL_TIMEOUT.run("assertViewAutofillState(" + view + ", " + expected + ")",
1531                 () -> {
1532                     final boolean actual = view.isAutofilled();
1533                     Log.v(TAG, "assertViewAutofillState(): expected=" + expected + ", actual="
1534                             + actual);
1535                     return actual == expected ? "not_used" : null;
1536                 });
1537     }
1538 
1539     /**
1540      * Allows the test to draw overlaid windows.
1541      *
1542      * <p>Should call {@link #disallowOverlays()} afterwards.
1543      */
allowOverlays()1544     public static void allowOverlays() {
1545         ShellUtils.setOverlayPermissions(MY_PACKAGE, true);
1546     }
1547 
1548     /**
1549      * Disallow the test to draw overlaid windows.
1550      *
1551      * <p>Should call {@link #disallowOverlays()} afterwards.
1552      */
disallowOverlays()1553     public static void disallowOverlays() {
1554         ShellUtils.setOverlayPermissions(MY_PACKAGE, false);
1555     }
1556 
createPresentation(String message)1557     public static RemoteViews createPresentation(String message) {
1558         final RemoteViews presentation = new RemoteViews(getContext()
1559                 .getPackageName(), R.layout.list_item);
1560         presentation.setTextViewText(R.id.text1, message);
1561         return presentation;
1562     }
1563 
createInlinePresentation(String message)1564     public static InlinePresentation createInlinePresentation(String message) {
1565         final PendingIntent dummyIntent = PendingIntent.getActivity(getContext(), 0, new Intent(),
1566                 PendingIntent.FLAG_IMMUTABLE);
1567         return createInlinePresentation(message, dummyIntent, false);
1568     }
1569 
createInlinePresentation(String message, PendingIntent attribution)1570     public static InlinePresentation createInlinePresentation(String message,
1571             PendingIntent attribution) {
1572         return createInlinePresentation(message, attribution, false);
1573     }
1574 
createPinnedInlinePresentation(String message)1575     public static InlinePresentation createPinnedInlinePresentation(String message) {
1576         final PendingIntent dummyIntent = PendingIntent.getActivity(getContext(), 0, new Intent(),
1577                 PendingIntent.FLAG_IMMUTABLE);
1578         return createInlinePresentation(message, dummyIntent, true);
1579     }
1580 
createInlinePresentation(@onNull String message, @NonNull PendingIntent attribution, boolean pinned)1581     private static InlinePresentation createInlinePresentation(@NonNull String message,
1582             @NonNull PendingIntent attribution, boolean pinned) {
1583         return new InlinePresentation(
1584                 InlineSuggestionUi.newContentBuilder(attribution)
1585                         .setTitle(message).build().getSlice(),
1586                 new InlinePresentationSpec.Builder(new Size(100, 100), new Size(400, 100))
1587                         .build(), /* pinned= */ pinned);
1588     }
1589 
createInlineTooltipPresentation( @onNull String message)1590     public static InlinePresentation createInlineTooltipPresentation(
1591             @NonNull String message) {
1592         final PendingIntent dummyIntent = PendingIntent.getActivity(getContext(), 0, new Intent(),
1593                 PendingIntent.FLAG_IMMUTABLE);
1594         return createInlineTooltipPresentation(message, dummyIntent);
1595     }
1596 
createInlineTooltipPresentation( @onNull String message, @NonNull PendingIntent attribution)1597     private static InlinePresentation createInlineTooltipPresentation(
1598             @NonNull String message, @NonNull PendingIntent attribution) {
1599         return InlinePresentation.createTooltipPresentation(
1600                 InlineSuggestionUi.newContentBuilder(attribution)
1601                         .setTitle(message).build().getSlice(),
1602                 new InlinePresentationSpec.Builder(new Size(100, 100), new Size(400, 100))
1603                         .build());
1604     }
1605 
mockSwitchInputMethod(@onNull Context context)1606     public static void mockSwitchInputMethod(@NonNull Context context) throws Exception {
1607         final ContentResolver cr = context.getContentResolver();
1608         final int subtype = Settings.Secure.getInt(cr, SELECTED_INPUT_METHOD_SUBTYPE);
1609         Settings.Secure.putInt(cr, SELECTED_INPUT_METHOD_SUBTYPE, subtype);
1610     }
1611 
1612     /**
1613      * Reset AutofillOptions to avoid cts package was added to augmented autofill allowlist.
1614      */
resetApplicationAutofillOptions(@onNull Context context)1615     public static void resetApplicationAutofillOptions(@NonNull Context context) {
1616         AutofillOptions options = AutofillOptions.forWhitelistingItself();
1617         options.augmentedAutofillEnabled = false;
1618         context.getApplicationContext().setAutofillOptions(options);
1619     }
1620 
1621     /**
1622      * Clear AutofillOptions.
1623      */
clearApplicationAutofillOptions(@onNull Context context)1624     public static void clearApplicationAutofillOptions(@NonNull Context context) {
1625         context.getApplicationContext().setAutofillOptions(null);
1626     }
1627 
Helper()1628     private Helper() {
1629         throw new UnsupportedOperationException("contain static methods only");
1630     }
1631 
1632     public static class FieldClassificationResult {
1633         public final AutofillId id;
1634         public final String[] categoryIds;
1635         public final float[] scores;
1636 
FieldClassificationResult(@onNull AutofillId id, @NonNull String categoryId, float score)1637         public FieldClassificationResult(@NonNull AutofillId id, @NonNull String categoryId,
1638                 float score) {
1639             this(id, new String[]{categoryId}, new float[]{score});
1640         }
1641 
FieldClassificationResult(@onNull AutofillId id, @NonNull String[] categoryIds, float[] scores)1642         public FieldClassificationResult(@NonNull AutofillId id, @NonNull String[] categoryIds,
1643                 float[] scores) {
1644             this.id = id;
1645             this.categoryIds = categoryIds;
1646             this.scores = scores;
1647         }
1648     }
1649 }
1650