xref: /aosp_15_r20/cts/tests/autofillservice/src/android/autofillservice/cts/testcore/Helper.java (revision b7c941bb3fa97aba169d73cee0bed2de8ac964bf)
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;
24 import static android.service.autofill.FillEventHistory.Event.TYPE_AUTHENTICATION_SELECTED;
25 import static android.service.autofill.FillEventHistory.Event.TYPE_CONTEXT_COMMITTED;
26 import static android.service.autofill.FillEventHistory.Event.TYPE_DATASETS_SHOWN;
27 import static android.service.autofill.FillEventHistory.Event.TYPE_DATASET_AUTHENTICATION_SELECTED;
28 import static android.service.autofill.FillEventHistory.Event.TYPE_DATASET_SELECTED;
29 import static android.service.autofill.FillEventHistory.Event.TYPE_SAVE_SHOWN;
30 import static android.service.autofill.FillEventHistory.Event.TYPE_VIEW_REQUESTED_AUTOFILL;
31 
32 import static com.android.compatibility.common.util.ShellUtils.runShellCommand;
33 
34 import static com.google.common.truth.Truth.assertThat;
35 import static com.google.common.truth.Truth.assertWithMessage;
36 
37 import android.app.Activity;
38 import android.app.ActivityOptions;
39 import android.app.Instrumentation;
40 import android.app.PendingIntent;
41 import android.app.assist.AssistStructure;
42 import android.app.assist.AssistStructure.ViewNode;
43 import android.app.assist.AssistStructure.WindowNode;
44 import android.autofillservice.cts.R;
45 import android.autofillservice.cts.activities.AbstractAutoFillActivity;
46 import android.content.AutofillOptions;
47 import android.content.ComponentName;
48 import android.content.ContentResolver;
49 import android.content.Context;
50 import android.content.Intent;
51 import android.content.pm.PackageManager;
52 import android.content.res.Resources;
53 import android.graphics.Bitmap;
54 import android.hardware.devicestate.DeviceState;
55 import android.hardware.devicestate.DeviceStateManager;
56 import android.hardware.devicestate.DeviceStateManager.DeviceStateCallback;
57 import android.icu.util.Calendar;
58 import android.os.Bundle;
59 import android.os.Environment;
60 import android.os.UserManager;
61 import android.provider.DeviceConfig;
62 import android.provider.Settings;
63 import android.service.autofill.CustomDescription;
64 import android.service.autofill.FieldClassification;
65 import android.service.autofill.FieldClassification.Match;
66 import android.service.autofill.FillContext;
67 import android.service.autofill.FillEventHistory;
68 import android.service.autofill.InlinePresentation;
69 import android.text.TextUtils;
70 import android.util.Log;
71 import android.util.Pair;
72 import android.util.Size;
73 import android.view.View;
74 import android.view.ViewGroup;
75 import android.view.ViewStructure.HtmlInfo;
76 import android.view.WindowInsets;
77 import android.view.autofill.AutofillFeatureFlags;
78 import android.view.autofill.AutofillId;
79 import android.view.autofill.AutofillManager;
80 import android.view.autofill.AutofillManager.AutofillCallback;
81 import android.view.autofill.AutofillValue;
82 import android.webkit.WebView;
83 import android.widget.RemoteViews;
84 import android.widget.inline.InlinePresentationSpec;
85 
86 import androidx.annotation.NonNull;
87 import androidx.annotation.Nullable;
88 import androidx.autofill.inline.v1.InlineSuggestionUi;
89 import androidx.test.platform.app.InstrumentationRegistry;
90 import androidx.test.runner.lifecycle.ActivityLifecycleMonitorRegistry;
91 import androidx.test.runner.lifecycle.Stage;
92 
93 import com.android.compatibility.common.util.BitmapUtils;
94 import com.android.compatibility.common.util.DeviceConfigStateManager;
95 import com.android.compatibility.common.util.OneTimeSettingsListener;
96 import com.android.compatibility.common.util.ShellUtils;
97 import com.android.compatibility.common.util.TestNameUtils;
98 import com.android.compatibility.common.util.Timeout;
99 import com.android.compatibility.common.util.UserSettings;
100 
101 import java.io.File;
102 import java.io.IOException;
103 import java.util.ArrayList;
104 import java.util.Arrays;
105 import java.util.Collection;
106 import java.util.List;
107 import java.util.Map;
108 import java.util.Map.Entry;
109 import java.util.concurrent.BlockingQueue;
110 import java.util.concurrent.TimeUnit;
111 import java.util.function.Consumer;
112 import java.util.function.Function;
113 import java.util.regex.Pattern;
114 
115 /**
116  * Helper for common funcionalities.
117  */
118 public final class Helper {
119 
120     public static final String TAG = "AutoFillCtsHelper";
121 
122     public static final boolean VERBOSE = false;
123 
124     public static final String MY_PACKAGE = "android.autofillservice.cts";
125 
126     public static final String ID_USERNAME_LABEL = "username_label";
127     public static final String ID_USERNAME = "username";
128     public static final String ID_PASSWORD_LABEL = "password_label";
129     public static final String ID_PASSWORD = "password";
130     public static final String ID_CARD_NUMBER = "card_number";
131     public static final String ID_LOGIN = "login";
132     public static final String ID_OUTPUT = "output";
133     public static final String ID_STATIC_TEXT = "static_text";
134     public static final String ID_EMPTY = "empty";
135     public static final String ID_CANCEL_FILL = "cancel_fill";
136     public static final String ID_IMEACTION_TEXT = "ime_option_text";
137     public static final String ID_IMEACTION_TEXT_IMPORTANT_FOR_AUTOFILL =
138         "ime_option_text_important_for_autofill";
139     public static final String ID_IMEACTION_LABEL = "ime_option_text_label";
140 
141     public static final String NULL_DATASET_ID = null;
142 
143     public static final char LARGE_STRING_CHAR = '6';
144     // NOTE: cannot be much large as it could ANR and fail the test.
145     public static final int LARGE_STRING_SIZE = 100_000;
146     public static final String LARGE_STRING = com.android.compatibility.common.util.TextUtils
147             .repeat(LARGE_STRING_CHAR, LARGE_STRING_SIZE);
148 
149     /**
150      * Can be used in cases where the autofill values is required by irrelevant (like adding a
151      * value to an authenticated dataset).
152      */
153     public static final String UNUSED_AUTOFILL_VALUE = null;
154 
155     private static final String ACCELLEROMETER_CHANGE =
156             "content insert --uri content://settings/system --bind name:s:accelerometer_rotation "
157                     + "--bind value:i:%d";
158 
159     private static final String LOCAL_DIRECTORY = Environment.getExternalStorageDirectory()
160             + "/CtsAutoFillServiceTestCases";
161 
162     private static final Timeout SETTINGS_BASED_SHELL_CMD_TIMEOUT = new Timeout(
163             "SETTINGS_SHELL_CMD_TIMEOUT", OneTimeSettingsListener.DEFAULT_TIMEOUT_MS / 2, 2,
164             OneTimeSettingsListener.DEFAULT_TIMEOUT_MS);
165 
166     public static final String DEVICE_CONFIG_AUTOFILL_DIALOG_HINTS = "autofill_dialog_hints";
167 
168     private static final UserSettings sUserSettings = new UserSettings();
169 
170     /**
171      * Helper interface used to filter nodes.
172      *
173      * @param <T> node type
174      */
175     interface NodeFilter<T> {
176         /**
177          * Returns whether the node passes the filter for such given id.
178          */
matches(T node, Object id)179         boolean matches(T node, Object id);
180     }
181 
182     private static final NodeFilter<ViewNode> RESOURCE_ID_FILTER = (node, id) -> {
183         return id.equals(node.getIdEntry());
184     };
185 
186     private static final NodeFilter<ViewNode> HTML_NAME_FILTER = (node, id) -> {
187         return id.equals(getHtmlName(node));
188     };
189 
190     private static final NodeFilter<ViewNode> HTML_NAME_OR_RESOURCE_ID_FILTER = (node, id) -> {
191         return id.equals(getHtmlName(node)) || id.equals(node.getIdEntry());
192     };
193 
194     private static final NodeFilter<ViewNode> TEXT_FILTER = (node, id) -> {
195         return id.equals(node.getText());
196     };
197 
198     private static final NodeFilter<ViewNode> AUTOFILL_HINT_FILTER = (node, id) -> {
199         return hasHint(node.getAutofillHints(), id);
200     };
201 
202     private static final NodeFilter<ViewNode> WEBVIEW_FORM_FILTER = (node, id) -> {
203         final String className = node.getClassName();
204         if (!className.equals("android.webkit.WebView")) return false;
205 
206         final HtmlInfo htmlInfo = assertHasHtmlTag(node, "form");
207         final String formName = getAttributeValue(htmlInfo, "name");
208         return id.equals(formName);
209     };
210 
211     private static final NodeFilter<View> AUTOFILL_HINT_VIEW_FILTER = (view, id) -> {
212         return hasHint(view.getAutofillHints(), id);
213     };
214 
toString(AssistStructure structure, StringBuilder builder)215     private static String toString(AssistStructure structure, StringBuilder builder) {
216         builder.append("[component=").append(structure.getActivityComponent());
217         final int nodes = structure.getWindowNodeCount();
218         for (int i = 0; i < nodes; i++) {
219             final WindowNode windowNode = structure.getWindowNodeAt(i);
220             dump(builder, windowNode.getRootViewNode(), " ", 0);
221         }
222         return builder.append(']').toString();
223     }
224 
225     @NonNull
toString(@onNull AssistStructure structure)226     public static String toString(@NonNull AssistStructure structure) {
227         return toString(structure, new StringBuilder());
228     }
229 
230     @Nullable
toString(@ullable AutofillValue value)231     public static String toString(@Nullable AutofillValue value) {
232         if (value == null) return null;
233         if (value.isText()) {
234             // We don't care about PII...
235             final CharSequence text = value.getTextValue();
236             return text == null ? null : text.toString();
237         }
238         return value.toString();
239     }
240 
241     /**
242      * Dump the assist structure on logcat.
243      */
dumpStructure(String message, AssistStructure structure)244     public static void dumpStructure(String message, AssistStructure structure) {
245         Log.i(TAG, toString(structure, new StringBuilder(message)));
246     }
247 
248     /**
249      * Dump the contexts on logcat.
250      */
dumpStructure(String message, List<FillContext> contexts)251     public static void dumpStructure(String message, List<FillContext> contexts) {
252         for (FillContext context : contexts) {
253             dumpStructure(message, context.getStructure());
254         }
255     }
256 
257     /**
258      * Dumps the state of the autofill service on logcat.
259      */
dumpAutofillService(@onNull String tag)260     public static void dumpAutofillService(@NonNull String tag) {
261         final String autofillDump = runShellCommand("dumpsys autofill");
262         Log.i(tag, "dumpsys autofill\n\n" + autofillDump);
263         final String myServiceDump = runShellCommand("dumpsys activity service %s",
264                 InstrumentedAutoFillService.SERVICE_NAME);
265         Log.i(tag, "my service dump: \n" + myServiceDump);
266     }
267 
268     /**
269      * Dumps the state of {@link android.service.autofill.InlineSuggestionRenderService}, and assert
270      * that it says the number of active inline suggestion views is the given number.
271      *
272      * <p>Note that ideally we should have a test api to fetch the number and verify against it.
273      * But at the time this test is added for Android 11, we have passed the deadline for adding
274      * the new test api, hence this approach.
275      */
assertActiveViewCountFromInlineSuggestionRenderService(int count)276     public static void assertActiveViewCountFromInlineSuggestionRenderService(int count) {
277         String response = runShellCommand(
278                 "dumpsys activity service .InlineSuggestionRenderService");
279         Log.d(TAG, "InlineSuggestionRenderService dump: " + response);
280         Pattern pattern = Pattern.compile(".*mActiveInlineSuggestions: " + count + ".*");
281         assertWithMessage("Expecting view count " + count
282                 + ", but seeing different count from service dumpsys " + response).that(
283                 pattern.matcher(response).find()).isTrue();
284     }
285 
286     /**
287      * Sets whether the user completed the initial setup.
288      */
setUserComplete(boolean complete)289     public static void setUserComplete(boolean complete) {
290         sUserSettings.syncSet(USER_SETUP_COMPLETE, complete ? "1" : null);
291     }
292 
dump(@onNull StringBuilder builder, @NonNull ViewNode node, @NonNull String prefix, int childId)293     private static void dump(@NonNull StringBuilder builder, @NonNull ViewNode node,
294             @NonNull String prefix, int childId) {
295         final int childrenSize = node.getChildCount();
296         builder.append("\n").append(prefix)
297             .append("child #").append(childId).append(':');
298         append(builder, "afId", node.getAutofillId());
299         append(builder, "afType", node.getAutofillType());
300         append(builder, "afValue", toString(node.getAutofillValue()));
301         append(builder, "resId", node.getIdEntry());
302         append(builder, "class", node.getClassName());
303         append(builder, "text", node.getText());
304         append(builder, "webDomain", node.getWebDomain());
305         append(builder, "checked", node.isChecked());
306         append(builder, "focused", node.isFocused());
307         final HtmlInfo htmlInfo = node.getHtmlInfo();
308         if (htmlInfo != null) {
309             builder.append(", HtmlInfo[tag=").append(htmlInfo.getTag())
310                 .append(", attrs: ").append(htmlInfo.getAttributes()).append(']');
311         }
312         if (childrenSize > 0) {
313             append(builder, "#children", childrenSize).append("\n").append(prefix);
314             prefix += " ";
315             if (childrenSize > 0) {
316                 for (int i = 0; i < childrenSize; i++) {
317                     dump(builder, node.getChildAt(i), prefix, i);
318                 }
319             }
320         }
321     }
322 
323     /**
324      * Appends a field value to a {@link StringBuilder} when it's not {@code null}.
325      */
326     @NonNull
append(@onNull StringBuilder builder, @NonNull String field, @Nullable Object value)327     public static StringBuilder append(@NonNull StringBuilder builder, @NonNull String field,
328             @Nullable Object value) {
329         if (value == null) return builder;
330 
331         if ((value instanceof Boolean) && ((Boolean) value)) {
332             return builder.append(", ").append(field);
333         }
334 
335         if (value instanceof Integer && ((Integer) value) == 0
336                 || value instanceof CharSequence && TextUtils.isEmpty((CharSequence) value)) {
337             return builder;
338         }
339 
340         return builder.append(", ").append(field).append('=').append(value);
341     }
342 
343     /**
344      * Appends a field value to a {@link StringBuilder} when it's {@code true}.
345      */
346     @NonNull
append(@onNull StringBuilder builder, @NonNull String field, boolean value)347     public static StringBuilder append(@NonNull StringBuilder builder, @NonNull String field,
348             boolean value) {
349         if (value) {
350             builder.append(", ").append(field);
351         }
352         return builder;
353     }
354 
355     /**
356      * Gets a node if it matches the filter criteria for the given id.
357      */
findNodeByFilter(@onNull AssistStructure structure, @NonNull Object id, @NonNull NodeFilter<ViewNode> filter)358     public static ViewNode findNodeByFilter(@NonNull AssistStructure structure, @NonNull Object id,
359             @NonNull NodeFilter<ViewNode> filter) {
360         Log.v(TAG, "Parsing request for activity " + structure.getActivityComponent());
361         final int nodes = structure.getWindowNodeCount();
362         for (int i = 0; i < nodes; i++) {
363             final WindowNode windowNode = structure.getWindowNodeAt(i);
364             final ViewNode rootNode = windowNode.getRootViewNode();
365             final ViewNode node = findNodeByFilter(rootNode, id, filter);
366             if (node != null) {
367                 return node;
368             }
369         }
370         return null;
371     }
372 
373     /**
374      * Gets a node if it matches the filter criteria for the given id.
375      */
findNodeByFilter(@onNull List<FillContext> contexts, @NonNull Object id, @NonNull NodeFilter<ViewNode> filter)376     public static ViewNode findNodeByFilter(@NonNull List<FillContext> contexts, @NonNull Object id,
377             @NonNull NodeFilter<ViewNode> filter) {
378         for (FillContext context : contexts) {
379             ViewNode node = findNodeByFilter(context.getStructure(), id, filter);
380             if (node != null) {
381                 return node;
382             }
383         }
384         return null;
385     }
386 
387     /**
388      * Gets a node if it matches the filter criteria for the given id.
389      */
findNodeByFilter(@onNull ViewNode node, @NonNull Object id, @NonNull NodeFilter<ViewNode> filter)390     public static ViewNode findNodeByFilter(@NonNull ViewNode node, @NonNull Object id,
391             @NonNull NodeFilter<ViewNode> filter) {
392         if (filter.matches(node, id)) {
393             return node;
394         }
395         final int childrenSize = node.getChildCount();
396         if (childrenSize > 0) {
397             for (int i = 0; i < childrenSize; i++) {
398                 final ViewNode found = findNodeByFilter(node.getChildAt(i), id, filter);
399                 if (found != null) {
400                     return found;
401                 }
402             }
403         }
404         return null;
405     }
406 
407     /**
408      * Gets a node given its Android resource id, or {@code null} if not found.
409      */
findNodeByResourceId(AssistStructure structure, String resourceId)410     public static ViewNode findNodeByResourceId(AssistStructure structure, String resourceId) {
411         return findNodeByFilter(structure, resourceId, RESOURCE_ID_FILTER);
412     }
413 
414     /**
415      * Gets a node given its Android resource id, or {@code null} if not found.
416      */
findNodeByResourceId(List<FillContext> contexts, String resourceId)417     public static ViewNode findNodeByResourceId(List<FillContext> contexts, String resourceId) {
418         return findNodeByFilter(contexts, resourceId, RESOURCE_ID_FILTER);
419     }
420 
421     /**
422      * Gets a node given its Android resource id, or {@code null} if not found.
423      */
findNodeByResourceId(ViewNode node, String resourceId)424     public static ViewNode findNodeByResourceId(ViewNode node, String resourceId) {
425         return findNodeByFilter(node, resourceId, RESOURCE_ID_FILTER);
426     }
427 
428     /**
429      * Gets a node given the name of its HTML INPUT tag, or {@code null} if not found.
430      */
findNodeByHtmlName(AssistStructure structure, String htmlName)431     public static ViewNode findNodeByHtmlName(AssistStructure structure, String htmlName) {
432         return findNodeByFilter(structure, htmlName, HTML_NAME_FILTER);
433     }
434 
435     /**
436      * Gets a node given the name of its HTML INPUT tag, or {@code null} if not found.
437      */
findNodeByHtmlName(List<FillContext> contexts, String htmlName)438     public static ViewNode findNodeByHtmlName(List<FillContext> contexts, String htmlName) {
439         return findNodeByFilter(contexts, htmlName, HTML_NAME_FILTER);
440     }
441 
442     /**
443      * Gets a node given the name of its HTML INPUT tag, or {@code null} if not found.
444      */
findNodeByHtmlName(ViewNode node, String htmlName)445     public static ViewNode findNodeByHtmlName(ViewNode node, String htmlName) {
446         return findNodeByFilter(node, htmlName, HTML_NAME_FILTER);
447     }
448 
449     /**
450      * Gets a node given the value of its (single) autofill hint property, or {@code null} if not
451      * found.
452      */
findNodeByAutofillHint(ViewNode node, String hint)453     public static ViewNode findNodeByAutofillHint(ViewNode node, String hint) {
454         return findNodeByFilter(node, hint, AUTOFILL_HINT_FILTER);
455     }
456 
457     /**
458      * Gets a node given the name of its HTML INPUT tag or Android resoirce id, or {@code null} if
459      * not found.
460      */
findNodeByHtmlNameOrResourceId(List<FillContext> contexts, String id)461     public static ViewNode findNodeByHtmlNameOrResourceId(List<FillContext> contexts, String id) {
462         return findNodeByFilter(contexts, id, HTML_NAME_OR_RESOURCE_ID_FILTER);
463     }
464 
465     /**
466      * Gets a node given its Android resource id.
467      */
468     @NonNull
findAutofillIdByResourceId(@onNull FillContext context, @NonNull String resourceId)469     public static AutofillId findAutofillIdByResourceId(@NonNull FillContext context,
470             @NonNull String resourceId) {
471         final ViewNode node = findNodeByFilter(context.getStructure(), resourceId,
472                 RESOURCE_ID_FILTER);
473         assertWithMessage("No node for resourceId %s", resourceId).that(node).isNotNull();
474         return node.getAutofillId();
475     }
476 
477     /**
478      * Gets the {@code name} attribute of a node representing an HTML input tag.
479      */
480     @Nullable
getHtmlName(@onNull ViewNode node)481     public static String getHtmlName(@NonNull ViewNode node) {
482         final HtmlInfo htmlInfo = node.getHtmlInfo();
483         if (htmlInfo == null) {
484             return null;
485         }
486         final String tag = htmlInfo.getTag();
487         if (!"input".equals(tag)) {
488             Log.w(TAG, "getHtmlName(): invalid tag (" + tag + ") on " + htmlInfo);
489             return null;
490         }
491         for (Pair<String, String> attr : htmlInfo.getAttributes()) {
492             if ("name".equals(attr.first)) {
493                 return attr.second;
494             }
495         }
496         Log.w(TAG, "getHtmlName(): no 'name' attribute on " + htmlInfo);
497         return null;
498     }
499 
500     /**
501      * Gets a node given its expected text, or {@code null} if not found.
502      */
findNodeByText(AssistStructure structure, String text)503     public static ViewNode findNodeByText(AssistStructure structure, String text) {
504         return findNodeByFilter(structure, text, TEXT_FILTER);
505     }
506 
507     /**
508      * Gets a node given its expected text, or {@code null} if not found.
509      */
findNodeByText(ViewNode node, String text)510     public static ViewNode findNodeByText(ViewNode node, String text) {
511         return findNodeByFilter(node, text, TEXT_FILTER);
512     }
513 
514     /**
515      * Gets a view that contains the an autofill hint, or {@code null} if not found.
516      */
findViewByAutofillHint(Activity activity, String hint)517     public static View findViewByAutofillHint(Activity activity, String hint) {
518         final View rootView = activity.getWindow().getDecorView().getRootView();
519         return findViewByAutofillHint(rootView, hint);
520     }
521 
522     /**
523      * Gets a view (or a descendant of it) that contains the an autofill hint, or {@code null} if
524      * not found.
525      */
findViewByAutofillHint(View view, String hint)526     public static View findViewByAutofillHint(View view, String hint) {
527         if (AUTOFILL_HINT_VIEW_FILTER.matches(view, hint)) return view;
528         if ((view instanceof ViewGroup)) {
529             final ViewGroup group = (ViewGroup) view;
530             for (int i = 0; i < group.getChildCount(); i++) {
531                 final View child = findViewByAutofillHint(group.getChildAt(i), hint);
532                 if (child != null) return child;
533             }
534         }
535         return null;
536     }
537 
538     /**
539      * Asserts a text-based node is sanitized.
540      */
assertTextIsSanitized(ViewNode node)541     public static void assertTextIsSanitized(ViewNode node) {
542         final CharSequence text = node.getText();
543         final String resourceId = node.getIdEntry();
544         if (!TextUtils.isEmpty(text)) {
545             throw new AssertionError("text on sanitized field " + resourceId + ": " + text);
546         }
547 
548         assertNotFromResources(node);
549         assertNodeHasNoAutofillValue(node);
550     }
551 
assertNotFromResources(ViewNode node)552     private static void assertNotFromResources(ViewNode node) {
553         assertThat(node.getTextIdEntry()).isNull();
554     }
555 
assertNodeHasNoAutofillValue(ViewNode node)556     public static void assertNodeHasNoAutofillValue(ViewNode node) {
557         final AutofillValue value = node.getAutofillValue();
558         if (value != null) {
559             final String text = value.isText() ? value.getTextValue().toString() : "N/A";
560             throw new AssertionError("node has value: " + value + " text=" + text);
561         }
562     }
563 
564     /**
565      * Asserts the contents of a text-based node that is also auto-fillable.
566      */
assertTextOnly(ViewNode node, String expectedValue)567     public static void assertTextOnly(ViewNode node, String expectedValue) {
568         assertText(node, expectedValue, false);
569         assertNotFromResources(node);
570     }
571 
572     /**
573      * Asserts the contents of a text-based node that is also auto-fillable.
574      */
assertTextOnly(AssistStructure structure, String resourceId, String expectedValue)575     public static void assertTextOnly(AssistStructure structure, String resourceId,
576             String expectedValue) {
577         final ViewNode node = findNodeByResourceId(structure, resourceId);
578         assertText(node, expectedValue, false);
579         assertNotFromResources(node);
580     }
581 
582     /**
583      * Asserts the contents of a text-based node that is also auto-fillable.
584      */
assertTextAndValue(ViewNode node, String expectedValue)585     public static void assertTextAndValue(ViewNode node, String expectedValue) {
586         assertText(node, expectedValue, true);
587         assertNotFromResources(node);
588     }
589 
590     /**
591      * Asserts a text-based node exists and verify its values.
592      */
assertTextAndValue(AssistStructure structure, String resourceId, String expectedValue)593     public static ViewNode assertTextAndValue(AssistStructure structure, String resourceId,
594             String expectedValue) {
595         final ViewNode node = findNodeByResourceId(structure, resourceId);
596         assertTextAndValue(node, expectedValue);
597         return node;
598     }
599 
600     /**
601      * Asserts a text-based node exists and is sanitized.
602      */
assertValue(AssistStructure structure, String resourceId, String expectedValue)603     public static ViewNode assertValue(AssistStructure structure, String resourceId,
604             String expectedValue) {
605         final ViewNode node = findNodeByResourceId(structure, resourceId);
606         assertTextValue(node, expectedValue);
607         return node;
608     }
609 
610     /**
611      * Asserts the values of a text-based node whose string come from resoruces.
612      */
assertTextFromResources(AssistStructure structure, String resourceId, String expectedValue, boolean isAutofillable, String expectedTextIdEntry)613     public static ViewNode assertTextFromResources(AssistStructure structure, String resourceId,
614             String expectedValue, boolean isAutofillable, String expectedTextIdEntry) {
615         final ViewNode node = findNodeByResourceId(structure, resourceId);
616         assertText(node, expectedValue, isAutofillable);
617         assertThat(node.getTextIdEntry()).isEqualTo(expectedTextIdEntry);
618         return node;
619     }
620 
assertHintFromResources(AssistStructure structure, String resourceId, String expectedValue, String expectedHintIdEntry)621     public static ViewNode assertHintFromResources(AssistStructure structure, String resourceId,
622             String expectedValue, String expectedHintIdEntry) {
623         final ViewNode node = findNodeByResourceId(structure, resourceId);
624         assertThat(node.getHint()).isEqualTo(expectedValue);
625         assertThat(node.getHintIdEntry()).isEqualTo(expectedHintIdEntry);
626         return node;
627     }
628 
assertText(ViewNode node, String expectedValue, boolean isAutofillable)629     private static void assertText(ViewNode node, String expectedValue, boolean isAutofillable) {
630         assertWithMessage("wrong text on %s", node.getAutofillId()).that(node.getText().toString())
631                 .isEqualTo(expectedValue);
632         final AutofillValue value = node.getAutofillValue();
633         final AutofillId id = node.getAutofillId();
634         if (isAutofillable) {
635             assertWithMessage("null auto-fill value on %s", id).that(value).isNotNull();
636             assertWithMessage("wrong auto-fill value on %s", id)
637                     .that(value.getTextValue().toString()).isEqualTo(expectedValue);
638         } else {
639             assertWithMessage("node %s should not have AutofillValue", id).that(value).isNull();
640         }
641     }
642 
643     /**
644      * Asserts the auto-fill value of a text-based node.
645      */
assertTextValue(ViewNode node, String expectedText)646     public static ViewNode assertTextValue(ViewNode node, String expectedText) {
647         final AutofillValue value = node.getAutofillValue();
648         final AutofillId id = node.getAutofillId();
649         assertWithMessage("null autofill value on %s", id).that(value).isNotNull();
650         assertWithMessage("wrong autofill type on %s", id).that(value.isText()).isTrue();
651         assertWithMessage("wrong autofill value on %s", id).that(value.getTextValue().toString())
652                 .isEqualTo(expectedText);
653         return node;
654     }
655 
656     /**
657      * Asserts the auto-fill value of a list-based node.
658      */
assertListValue(ViewNode node, int expectedIndex)659     public static ViewNode assertListValue(ViewNode node, int expectedIndex) {
660         final AutofillValue value = node.getAutofillValue();
661         final AutofillId id = node.getAutofillId();
662         assertWithMessage("null autofill value on %s", id).that(value).isNotNull();
663         assertWithMessage("wrong autofill type on %s", id).that(value.isList()).isTrue();
664         assertWithMessage("wrong autofill value on %s", id).that(value.getListValue())
665                 .isEqualTo(expectedIndex);
666         return node;
667     }
668 
669     /**
670      * Asserts the auto-fill value of a toggle-based node.
671      */
assertToggleValue(ViewNode node, boolean expectedToggle)672     public static void assertToggleValue(ViewNode node, boolean expectedToggle) {
673         final AutofillValue value = node.getAutofillValue();
674         final AutofillId id = node.getAutofillId();
675         assertWithMessage("null autofill value on %s", id).that(value).isNotNull();
676         assertWithMessage("wrong autofill type on %s", id).that(value.isToggle()).isTrue();
677         assertWithMessage("wrong autofill value on %s", id).that(value.getToggleValue())
678                 .isEqualTo(expectedToggle);
679     }
680 
681     /**
682      * Asserts the auto-fill value of a date-based node.
683      */
assertDateValue(Object object, AutofillValue value, int year, int month, int day)684     public static void assertDateValue(Object object, AutofillValue value, int year, int month,
685             int day) {
686         assertWithMessage("null autofill value on %s", object).that(value).isNotNull();
687         assertWithMessage("wrong autofill type on %s", object).that(value.isDate()).isTrue();
688 
689         final Calendar cal = Calendar.getInstance();
690         cal.setTimeInMillis(value.getDateValue());
691 
692         assertWithMessage("Wrong year on AutofillValue %s", value)
693             .that(cal.get(Calendar.YEAR)).isEqualTo(year);
694         assertWithMessage("Wrong month on AutofillValue %s", value)
695             .that(cal.get(Calendar.MONTH)).isEqualTo(month);
696         assertWithMessage("Wrong day on AutofillValue %s", value)
697              .that(cal.get(Calendar.DAY_OF_MONTH)).isEqualTo(day);
698     }
699 
700     /**
701      * Asserts the auto-fill value of a date-based node.
702      */
assertDateValue(ViewNode node, int year, int month, int day)703     public static void assertDateValue(ViewNode node, int year, int month, int day) {
704         assertDateValue(node, node.getAutofillValue(), year, month, day);
705     }
706 
707     /**
708      * Asserts the auto-fill value of a date-based view.
709      */
assertDateValue(View view, int year, int month, int day)710     public static void assertDateValue(View view, int year, int month, int day) {
711         assertDateValue(view, view.getAutofillValue(), year, month, day);
712     }
713 
714     /**
715      * Asserts the auto-fill value of a time-based node.
716      */
assertTimeValue(Object object, AutofillValue value, int hour, int minute)717     private static void assertTimeValue(Object object, AutofillValue value, int hour, int minute) {
718         assertWithMessage("null autofill value on %s", object).that(value).isNotNull();
719         assertWithMessage("wrong autofill type on %s", object).that(value.isDate()).isTrue();
720 
721         final Calendar cal = Calendar.getInstance();
722         cal.setTimeInMillis(value.getDateValue());
723 
724         assertWithMessage("Wrong hour on AutofillValue %s", value)
725             .that(cal.get(Calendar.HOUR_OF_DAY)).isEqualTo(hour);
726         assertWithMessage("Wrong minute on AutofillValue %s", value)
727             .that(cal.get(Calendar.MINUTE)).isEqualTo(minute);
728     }
729 
730     /**
731      * Asserts the auto-fill value of a time-based node.
732      */
assertTimeValue(ViewNode node, int hour, int minute)733     public static void assertTimeValue(ViewNode node, int hour, int minute) {
734         assertTimeValue(node, node.getAutofillValue(), hour, minute);
735     }
736 
737     /**
738      * Asserts the auto-fill value of a time-based view.
739      */
assertTimeValue(View view, int hour, int minute)740     public static void assertTimeValue(View view, int hour, int minute) {
741         assertTimeValue(view, view.getAutofillValue(), hour, minute);
742     }
743 
744     /**
745      * Asserts a text-based node exists and is sanitized.
746      */
assertTextIsSanitized(AssistStructure structure, String resourceId)747     public static ViewNode assertTextIsSanitized(AssistStructure structure, String resourceId) {
748         final ViewNode node = findNodeByResourceId(structure, resourceId);
749         assertWithMessage("no ViewNode with id %s", resourceId).that(node).isNotNull();
750         assertTextIsSanitized(node);
751         return node;
752     }
753 
754     /**
755      * Asserts a list-based node exists and is sanitized.
756      */
assertListValueIsSanitized(AssistStructure structure, String resourceId)757     public static void assertListValueIsSanitized(AssistStructure structure, String resourceId) {
758         final ViewNode node = findNodeByResourceId(structure, resourceId);
759         assertWithMessage("no ViewNode with id %s", resourceId).that(node).isNotNull();
760         assertTextIsSanitized(node);
761     }
762 
763     /**
764      * Asserts a toggle node exists and is sanitized.
765      */
assertToggleIsSanitized(AssistStructure structure, String resourceId)766     public static void assertToggleIsSanitized(AssistStructure structure, String resourceId) {
767         final ViewNode node = findNodeByResourceId(structure, resourceId);
768         assertNodeHasNoAutofillValue(node);
769         assertWithMessage("ViewNode %s should not be checked", resourceId).that(node.isChecked())
770                 .isFalse();
771     }
772 
773     /**
774      * Asserts a node exists and has the {@code expected} number of children.
775      */
assertNumberOfChildren(AssistStructure structure, String resourceId, int expected)776     public static void assertNumberOfChildren(AssistStructure structure, String resourceId,
777             int expected) {
778         final ViewNode node = findNodeByResourceId(structure, resourceId);
779         final int actual = node.getChildCount();
780         if (actual != expected) {
781             dumpStructure("assertNumberOfChildren()", structure);
782             throw new AssertionError("assertNumberOfChildren() for " + resourceId
783                     + " failed: expected " + expected + ", got " + actual);
784         }
785     }
786 
787     /**
788      * Asserts the number of children in the Assist structure.
789      */
assertNumberOfChildrenWithWindowTitle(AssistStructure structure, int expected, CharSequence windowTitle)790     public static void assertNumberOfChildrenWithWindowTitle(AssistStructure structure,
791             int expected, CharSequence windowTitle) {
792         final int actual = getNumberNodes(structure, windowTitle);
793         if (actual != expected) {
794             dumpStructure("assertNumberOfChildren()", structure);
795             throw new AssertionError("assertNumberOfChildren() for structure failed: expected "
796                     + expected + ", got " + actual);
797         }
798     }
799 
800     /**
801      * Gets the total number of nodes in an structure.
802      * A node that has a non-null IdPackage which does not match the test package is not counted.
803      */
getNumberNodes(AssistStructure structure, CharSequence windowTitle)804     public static int getNumberNodes(AssistStructure structure,
805             CharSequence windowTitle) {
806         int count = 0;
807         final int nodes = structure.getWindowNodeCount();
808         for (int i = 0; i < nodes; i++) {
809             final WindowNode windowNode = structure.getWindowNodeAt(i);
810             if (windowNode.getTitle().equals(windowTitle)) {
811                 final ViewNode rootNode = windowNode.getRootViewNode();
812                 count += getNumberNodes(rootNode);
813             }
814         }
815         return count;
816     }
817 
818     /**
819      * Gets the activity title.
820      */
getActivityTitle(Instrumentation instrumentation, Activity activity)821     public static CharSequence getActivityTitle(Instrumentation instrumentation,
822             Activity activity) {
823         final StringBuilder titleBuilder = new StringBuilder();
824         instrumentation.runOnMainSync(() -> titleBuilder.append(activity.getTitle()));
825         return titleBuilder;
826     }
827 
828     /**
829      * Gets the total number of nodes in an node, including all descendants and the node itself.
830      * A node that has a non-null IdPackage which does not match the test package is not counted.
831      */
getNumberNodes(ViewNode node)832     public static int getNumberNodes(ViewNode node) {
833         if (node.getIdPackage() != null && !node.getIdPackage().equals(MY_PACKAGE)) {
834             Log.w(TAG, "ViewNode ignored in getNumberNodes because of mismatched package: "
835                     + node.getIdPackage());
836             return 0;
837         }
838         int count = 1;
839         final int childrenSize = node.getChildCount();
840         for (int i = 0; i < childrenSize; i++) {
841             count += getNumberNodes(node.getChildAt(i));
842         }
843         return count;
844     }
845 
846     /**
847      * Creates an array of {@link AutofillId} mapped from the {@code structure} nodes with the given
848      * {@code resourceIds}.
849      */
getAutofillIds(Function<String, AutofillId> autofillIdResolver, String[] resourceIds)850     public static AutofillId[] getAutofillIds(Function<String, AutofillId> autofillIdResolver,
851             String[] resourceIds) {
852         if (resourceIds == null) return null;
853 
854         final AutofillId[] requiredIds = new AutofillId[resourceIds.length];
855         for (int i = 0; i < resourceIds.length; i++) {
856             final String resourceId = resourceIds[i];
857             requiredIds[i] = autofillIdResolver.apply(resourceId);
858         }
859         return requiredIds;
860     }
861 
862     /**
863      * Prevents the screen to rotate by itself
864      */
disableAutoRotation(UiBot uiBot)865     public static void disableAutoRotation(UiBot uiBot) throws Exception {
866         runShellCommand(ACCELLEROMETER_CHANGE, 0);
867         uiBot.setScreenOrientation(PORTRAIT);
868     }
869 
870     /**
871      * Allows the screen to rotate by itself
872      */
allowAutoRotation()873     public static void allowAutoRotation() {
874         runShellCommand(ACCELLEROMETER_CHANGE, 1);
875     }
876 
877     /**
878      * Gets the maximum number of partitions per session.
879      */
getMaxPartitions()880     public static int getMaxPartitions() {
881         return Integer.parseInt(runShellCommand("cmd autofill get max_partitions"));
882     }
883 
884     /**
885      * Sets the maximum number of partitions per session.
886      */
setMaxPartitions(int value)887     public static void setMaxPartitions(int value) throws Exception {
888         runShellCommand("cmd autofill set max_partitions %d", value);
889         SETTINGS_BASED_SHELL_CMD_TIMEOUT.run("get max_partitions", () -> {
890             return getMaxPartitions() == value ? Boolean.TRUE : null;
891         });
892     }
893 
894     /**
895      * Gets the maximum number of visible datasets.
896      */
getMaxVisibleDatasets()897     public static int getMaxVisibleDatasets() {
898         return Integer.parseInt(runShellCommand("cmd autofill get max_visible_datasets"));
899     }
900 
901     /**
902      * Sets the maximum number of visible datasets.
903      */
setMaxVisibleDatasets(int value)904     public static void setMaxVisibleDatasets(int value) throws Exception {
905         runShellCommand("cmd autofill set max_visible_datasets %d", value);
906         SETTINGS_BASED_SHELL_CMD_TIMEOUT.run("get max_visible_datasets", () -> {
907             return getMaxVisibleDatasets() == value ? Boolean.TRUE : null;
908         });
909     }
910 
911     /**
912      * Checks if autofill window is fullscreen, see com.android.server.autofill.ui.FillUi.
913      */
isAutofillWindowFullScreen(Context context)914     public static boolean isAutofillWindowFullScreen(Context context) {
915         return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK);
916     }
917 
918     /**
919      * Checks if PCC is enabled for the device
920      */
isPccSupported(Context context)921     public static boolean isPccSupported(Context context) {
922         final PackageManager packageManager = context.getPackageManager();
923         if (packageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
924             Log.v(TAG, "isPccSupported(): is auto");
925             return false;
926         }
927         if (packageManager.hasSystemFeature(PackageManager.FEATURE_PC)) {
928             Log.v(TAG, "isPccSupported(): is PC");
929             return false;
930         }
931         return true;
932     }
933 
934     /**
935      * Returns if devices is Automotive device
936      */
isAutomotive(Context context)937     public static boolean isAutomotive(Context context) {
938         final PackageManager packageManager = context.getPackageManager();
939         return packageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE);
940     }
941 
isMainUser(Context context)942     public static boolean isMainUser(Context context) {
943         boolean mainUser = false;
944         try {
945             final UserManager userManager = context.getSystemService(UserManager.class);
946             mainUser = userManager.isMainUser();
947         } catch (SecurityException ex) {
948             // Nothing
949             mainUser = false;
950         }
951 
952         return mainUser;
953 
954     }
955 
956     /**
957      * Checks if screen orientation can be changed.
958      */
isRotationSupported(Context context)959     public static boolean isRotationSupported(Context context) {
960         final PackageManager packageManager = context.getPackageManager();
961         if (packageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
962             Log.v(TAG, "isRotationSupported(): is auto");
963             return false;
964         }
965         if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK)) {
966             Log.v(TAG, "isRotationSupported(): has leanback feature");
967             return false;
968         }
969         if (packageManager.hasSystemFeature(PackageManager.FEATURE_PC)) {
970             Log.v(TAG, "isRotationSupported(): is PC");
971             return false;
972         }
973         if (!packageManager.hasSystemFeature(PackageManager.FEATURE_SCREEN_LANDSCAPE)
974                 || !packageManager.hasSystemFeature(PackageManager.FEATURE_SCREEN_PORTRAIT)) {
975             Log.v(TAG, "isRotationSupported(): no screen orientation feature");
976             return false;
977         }
978         return true;
979     }
980 
getBoolean(Context context, String id)981     private static boolean getBoolean(Context context, String id) {
982         final Resources resources = context.getResources();
983         final int booleanId = resources.getIdentifier(id, "bool", "android");
984         return resources.getBoolean(booleanId);
985     }
986 
987     /**
988      * Uses Shell command to get the Autofill logging level.
989      */
getLoggingLevel()990     public static String getLoggingLevel() {
991         return runShellCommand("cmd autofill get log_level");
992     }
993 
994     /**
995      * Uses Shell command to set the Autofill logging level.
996      */
setLoggingLevel(String level)997     public static void setLoggingLevel(String level) {
998         runShellCommand("cmd autofill set log_level %s", level);
999     }
1000 
1001     /**
1002      * Uses Settings to enable the given autofill service for the default user, and checks the
1003      * value was properly check, throwing an exception if it was not.
1004      */
enableAutofillService(String serviceName)1005     public static void enableAutofillService(String serviceName) {
1006         if (isAutofillServiceEnabled(serviceName)) return;
1007 
1008         // Sets the setting synchronously. Note that the config itself is sets synchronously but
1009         // launch of the service is asynchronous after the config is updated.
1010         sUserSettings.syncSet(AUTOFILL_SERVICE, serviceName);
1011 
1012         // Waits until the service is actually enabled.
1013         try {
1014             Timeouts.CONNECTION_TIMEOUT.run("Enabling Autofill service", () -> {
1015                 return isAutofillServiceEnabled(serviceName) ? serviceName : null;
1016             });
1017         } catch (Exception e) {
1018             throw new AssertionError("Enabling Autofill service failed.");
1019         }
1020     }
1021 
1022     /**
1023      * Uses Settings to disable the given autofill service for the default user, and waits until
1024      * the setting is deleted.
1025      */
disableAutofillService()1026     public static void disableAutofillService() {
1027         final String currentService = sUserSettings.get(AUTOFILL_SERVICE);
1028         if (currentService == null) {
1029             Log.v(TAG, "disableAutofillService(): already disabled");
1030             return;
1031         }
1032         Log.v(TAG, "Disabling " + currentService);
1033         sUserSettings.syncDelete(AUTOFILL_SERVICE);
1034     }
1035 
1036     /**
1037      * Checks whether the given service is set as the autofill service for the default user.
1038      */
isAutofillServiceEnabled(@onNull String serviceName)1039     public static boolean isAutofillServiceEnabled(@NonNull String serviceName) {
1040         final String actualName = getAutofillServiceName();
1041         return serviceName.equals(actualName);
1042     }
1043 
1044     /**
1045      * Gets then name of the autofill service for the default user.
1046      */
getAutofillServiceName()1047     public static String getAutofillServiceName() {
1048         return sUserSettings.get(AUTOFILL_SERVICE);
1049     }
1050 
1051     /**
1052      * Asserts whether the given service is enabled as the autofill service for the default user.
1053      */
assertAutofillServiceStatus(@onNull String serviceName, boolean enabled)1054     public static void assertAutofillServiceStatus(@NonNull String serviceName, boolean enabled) {
1055         final String actual = sUserSettings.get(AUTOFILL_SERVICE);
1056         final String expected = enabled ? serviceName : null;
1057         assertWithMessage("Invalid value for secure setting %s", AUTOFILL_SERVICE)
1058                 .that(actual).isEqualTo(expected);
1059     }
1060 
1061     /**
1062      * Enables / disables the default augmented autofill service.
1063      */
setDefaultAugmentedAutofillServiceEnabled(boolean enabled)1064     public static void setDefaultAugmentedAutofillServiceEnabled(boolean enabled) {
1065         Log.d(TAG, "setDefaultAugmentedAutofillServiceEnabled(): " + enabled);
1066         runShellCommand("cmd autofill set default-augmented-service-enabled 0 %s",
1067                 Boolean.toString(enabled));
1068     }
1069 
1070     /**
1071      * Sets the pcc detection service temporarily for 300 seconds.
1072      */
setAutofillDetectionService(String service)1073     public static void setAutofillDetectionService(String service) {
1074         Log.d(TAG, "setAutofillDetectionService");
1075         runShellCommand("cmd autofill set temporary-detection-service 0 %s 30000",
1076                 service);
1077     }
1078 
1079     /**
1080      * Reset the pcc detection service
1081      */
resetAutofillDetectionService()1082     public static void resetAutofillDetectionService() {
1083         Log.d(TAG, "resetAutofillDetectionService");
1084         runShellCommand("cmd autofill set temporary-detection-service 0");
1085     }
1086 
1087     /**
1088      * Gets the instrumentation context.
1089      */
getContext()1090     public static Context getContext() {
1091         return InstrumentationRegistry.getInstrumentation().getContext();
1092     }
1093 
1094     /**
1095      * Asserts the node has an {@code HTMLInfo} property, with the given tag.
1096      */
assertHasHtmlTag(ViewNode node, String expectedTag)1097     public static HtmlInfo assertHasHtmlTag(ViewNode node, String expectedTag) {
1098         final HtmlInfo info = node.getHtmlInfo();
1099         assertWithMessage("node doesn't have htmlInfo").that(info).isNotNull();
1100         assertWithMessage("wrong tag").that(info.getTag()).isEqualTo(expectedTag);
1101         return info;
1102     }
1103 
1104     /**
1105      * Gets the value of an {@code HTMLInfo} attribute.
1106      */
1107     @Nullable
getAttributeValue(HtmlInfo info, String attribute)1108     public static String getAttributeValue(HtmlInfo info, String attribute) {
1109         for (Pair<String, String> pair : info.getAttributes()) {
1110             if (pair.first.equals(attribute)) {
1111                 return pair.second;
1112             }
1113         }
1114         return null;
1115     }
1116 
1117     /**
1118      * Asserts a {@code HTMLInfo} has an attribute with a given value.
1119      */
assertHasAttribute(HtmlInfo info, String attribute, String expectedValue)1120     public static void assertHasAttribute(HtmlInfo info, String attribute, String expectedValue) {
1121         final String actualValue = getAttributeValue(info, attribute);
1122         assertWithMessage("Attribute %s not found", attribute).that(actualValue).isNotNull();
1123         assertWithMessage("Wrong value for Attribute %s", attribute)
1124             .that(actualValue).isEqualTo(expectedValue);
1125     }
1126 
1127     /**
1128      * Finds a {@link WebView} node given its expected form name.
1129      */
findWebViewNodeByFormName(AssistStructure structure, String formName)1130     public static ViewNode findWebViewNodeByFormName(AssistStructure structure, String formName) {
1131         return findNodeByFilter(structure, formName, WEBVIEW_FORM_FILTER);
1132     }
1133 
assertClientState(Object container, Bundle clientState, String key, String value)1134     private static void assertClientState(Object container, Bundle clientState,
1135             String key, String value) {
1136         assertWithMessage("'%s' should have client state", container)
1137             .that(clientState).isNotNull();
1138         assertWithMessage("Wrong number of client state extras on '%s'", container)
1139             .that(clientState.keySet().size()).isEqualTo(1);
1140         assertWithMessage("Wrong value for client state key (%s) on '%s'", key, container)
1141             .that(clientState.getString(key)).isEqualTo(value);
1142     }
1143 
1144     /**
1145      * Asserts the content of a {@link FillEventHistory#getClientState()}.
1146      *
1147      * @param history event to be asserted
1148      * @param key the only key expected in the client state bundle
1149      * @param value the only value expected in the client state bundle
1150      */
1151     @SuppressWarnings("javadoc")
assertDeprecatedClientState(@onNull FillEventHistory history, @NonNull String key, @NonNull String value)1152     public static void assertDeprecatedClientState(@NonNull FillEventHistory history,
1153             @NonNull String key, @NonNull String value) {
1154         assertThat(history).isNotNull();
1155         @SuppressWarnings("deprecation")
1156         final Bundle clientState = history.getClientState();
1157         assertClientState(history, clientState, key, value);
1158     }
1159 
1160     /**
1161      * Asserts the {@link FillEventHistory#getClientState()} is not set.
1162      *
1163      * @param history event to be asserted
1164      */
1165     @SuppressWarnings("javadoc")
assertNoDeprecatedClientState(@onNull FillEventHistory history)1166     public static void assertNoDeprecatedClientState(@NonNull FillEventHistory history) {
1167         assertThat(history).isNotNull();
1168         @SuppressWarnings("deprecation")
1169         final Bundle clientState = history.getClientState();
1170         assertWithMessage("History '%s' should not have client state", history)
1171              .that(clientState).isNull();
1172     }
1173 
assertFillEventPresentationType(FillEventHistory.Event event, int expectedType)1174     private static void assertFillEventPresentationType(FillEventHistory.Event event,
1175             int expectedType) {
1176         assertThat(event.getUiType()).isEqualTo(expectedType);
1177     }
1178 
1179     /**
1180      * Asserts the content of a {@link android.service.autofill.FillEventHistory.Event}.
1181      *
1182      * @param event event to be asserted
1183      * @param eventType expected type
1184      * @param datasetId dataset set id expected in the event
1185      * @param key the only key expected in the client state bundle (or {@code null} if it shouldn't
1186      * have client state)
1187      * @param value the only value expected in the client state bundle (or {@code null} if it
1188      * shouldn't have client state)
1189      * @param fieldClassificationResults expected results when asserting field classification
1190      */
assertFillEvent(@onNull FillEventHistory.Event event, int eventType, @Nullable String datasetId, @Nullable String key, @Nullable String value, @Nullable FieldClassificationResult[] fieldClassificationResults)1191     private static void assertFillEvent(@NonNull FillEventHistory.Event event,
1192             int eventType, @Nullable String datasetId,
1193             @Nullable String key, @Nullable String value,
1194             @Nullable FieldClassificationResult[] fieldClassificationResults) {
1195         assertThat(event).isNotNull();
1196         assertWithMessage("Wrong type for %s", event).that(event.getType()).isEqualTo(eventType);
1197         if (datasetId == null) {
1198             assertWithMessage("Event %s should not have dataset id", event)
1199                 .that(event.getDatasetId()).isNull();
1200         } else {
1201             assertWithMessage("Wrong dataset id for %s", event)
1202                 .that(event.getDatasetId()).isEqualTo(datasetId);
1203         }
1204         final Bundle clientState = event.getClientState();
1205         if (key == null) {
1206             assertWithMessage("Event '%s' should not have client state", event)
1207                 .that(clientState).isNull();
1208         } else {
1209             assertClientState(event, clientState, key, value);
1210         }
1211         assertWithMessage("Event '%s' should not have selected datasets", event)
1212                 .that(event.getSelectedDatasetIds()).isEmpty();
1213         assertWithMessage("Event '%s' should not have ignored datasets", event)
1214                 .that(event.getIgnoredDatasetIds()).isEmpty();
1215         assertWithMessage("Event '%s' should not have changed fields", event)
1216                 .that(event.getChangedFields()).isEmpty();
1217         assertWithMessage("Event '%s' should not have manually-entered fields", event)
1218                 .that(event.getManuallyEnteredField()).isEmpty();
1219         final Map<AutofillId, FieldClassification> detectedFields = event.getFieldsClassification();
1220         if (fieldClassificationResults == null) {
1221             assertThat(detectedFields).isEmpty();
1222         } else {
1223             assertThat(detectedFields).hasSize(fieldClassificationResults.length);
1224             int i = 0;
1225             for (Entry<AutofillId, FieldClassification> entry : detectedFields.entrySet()) {
1226                 assertMatches(i, entry, fieldClassificationResults[i]);
1227                 i++;
1228             }
1229         }
1230     }
1231 
assertMatches(int i, Entry<AutofillId, FieldClassification> actualResult, FieldClassificationResult expectedResult)1232     private static void assertMatches(int i, Entry<AutofillId, FieldClassification> actualResult,
1233             FieldClassificationResult expectedResult) {
1234         assertWithMessage("Wrong field id at index %s", i).that(actualResult.getKey())
1235                 .isEqualTo(expectedResult.id);
1236         final List<Match> matches = actualResult.getValue().getMatches();
1237         assertWithMessage("Wrong number of matches: " + matches).that(matches.size())
1238                 .isEqualTo(expectedResult.categoryIds.length);
1239         for (int j = 0; j < matches.size(); j++) {
1240             final Match match = matches.get(j);
1241             assertWithMessage("Wrong categoryId at (%s, %s): %s", i, j, match)
1242                 .that(match.getCategoryId()).isEqualTo(expectedResult.categoryIds[j]);
1243             assertWithMessage("Wrong score at (%s, %s): %s", i, j, match)
1244                 .that(match.getScore()).isWithin(0.01f).of(expectedResult.scores[j]);
1245         }
1246     }
1247 
1248     /**
1249      * Asserts the content of a
1250      * {@link android.service.autofill.FillEventHistory.Event#TYPE_DATASET_SELECTED} event.
1251      *
1252      * @param event event to be asserted
1253      * @param datasetId dataset set id expected in the event
1254      */
assertFillEventForDatasetSelected(@onNull FillEventHistory.Event event, @Nullable String datasetId, int uiType)1255     public static void assertFillEventForDatasetSelected(@NonNull FillEventHistory.Event event,
1256             @Nullable String datasetId, int uiType) {
1257         assertFillEvent(event, TYPE_DATASET_SELECTED, datasetId, null, null, null);
1258         assertFillEventPresentationType(event, uiType);
1259     }
1260 
1261     /**
1262      * Asserts that {@android.service.autofill.FillEventHistory.Event#TYPE_VIEW_REQUESTED_AUTOFILL}
1263      * is present in the FillEventHistory
1264      */
assertFillEventForViewEntered(@onNull FillEventHistory.Event event)1265     public static void assertFillEventForViewEntered(@NonNull FillEventHistory.Event event) {
1266         assertFillEvent(event, TYPE_VIEW_REQUESTED_AUTOFILL, null, null, null, null);
1267     }
1268 
1269     /**
1270      * Asserts the content of a
1271      * {@link android.service.autofill.FillEventHistory.Event#TYPE_DATASET_SELECTED} event.
1272      *
1273      * @param event event to be asserted
1274      * @param datasetId dataset set id expected in the event
1275      * @param key the only key expected in the client state bundle
1276      * @param value the only value expected in the client state bundle
1277      */
assertFillEventForDatasetSelected(@onNull FillEventHistory.Event event, @Nullable String datasetId, @Nullable String key, @Nullable String value, int uiType)1278     public static void assertFillEventForDatasetSelected(@NonNull FillEventHistory.Event event,
1279             @Nullable String datasetId, @Nullable String key, @Nullable String value, int uiType) {
1280         assertFillEvent(event, TYPE_DATASET_SELECTED, datasetId, key, value, null);
1281         assertFillEventPresentationType(event, uiType);
1282     }
1283 
1284     /**
1285      * Asserts the content of a
1286      * {@link android.service.autofill.FillEventHistory.Event#TYPE_SAVE_SHOWN} event.
1287      *
1288      * @param event event to be asserted
1289      * @param datasetId dataset set id expected in the event
1290      * @param key the only key expected in the client state bundle
1291      * @param value the only value expected in the client state bundle
1292      */
assertFillEventForSaveShown(@onNull FillEventHistory.Event event, @Nullable String datasetId, @NonNull String key, @NonNull String value)1293     public static void assertFillEventForSaveShown(@NonNull FillEventHistory.Event event,
1294             @Nullable String datasetId, @NonNull String key, @NonNull String value) {
1295         assertFillEvent(event, TYPE_SAVE_SHOWN, datasetId, key, value, null);
1296     }
1297 
1298     /**
1299      * Asserts the content of a
1300      * {@link android.service.autofill.FillEventHistory.Event#TYPE_SAVE_SHOWN} event.
1301      *
1302      * @param event event to be asserted
1303      * @param datasetId dataset set id expected in the event
1304      */
assertFillEventForSaveShown(@onNull FillEventHistory.Event event, @Nullable String datasetId)1305     public static void assertFillEventForSaveShown(@NonNull FillEventHistory.Event event,
1306             @Nullable String datasetId) {
1307         assertFillEvent(event, TYPE_SAVE_SHOWN, datasetId, null, null, null);
1308     }
1309 
1310     /**
1311      * Asserts the content of a
1312      * {@link android.service.autofill.FillEventHistory.Event#TYPE_DATASETS_SHOWN} event.
1313      *
1314      * @param event event to be asserted
1315      * @param key the only key expected in the client state bundle
1316      * @param value the only value expected in the client state bundle
1317      * @param uiType the expected ui presentation type
1318      */
assertFillEventForDatasetShown(@onNull FillEventHistory.Event event, @NonNull String key, @NonNull String value, int uiType)1319     public static void assertFillEventForDatasetShown(@NonNull FillEventHistory.Event event,
1320             @NonNull String key, @NonNull String value, int uiType) {
1321         assertFillEvent(event, TYPE_DATASETS_SHOWN, NULL_DATASET_ID, key, value, null);
1322         assertFillEventPresentationType(event, uiType);
1323     }
1324 
1325     /**
1326      * Asserts the content of a
1327      * {@link android.service.autofill.FillEventHistory.Event#TYPE_DATASETS_SHOWN} event.
1328      *
1329      * @param event event to be asserted
1330      */
assertFillEventForDatasetShown(@onNull FillEventHistory.Event event, int uiType)1331     public static void assertFillEventForDatasetShown(@NonNull FillEventHistory.Event event,
1332             int uiType) {
1333         assertFillEvent(event, TYPE_DATASETS_SHOWN, NULL_DATASET_ID, null, null, null);
1334         assertFillEventPresentationType(event, uiType);
1335     }
1336 
1337     /**
1338      * Asserts the content of a
1339      * {@link android.service.autofill.FillEventHistory.Event#TYPE_DATASET_AUTHENTICATION_SELECTED}
1340      * event.
1341      *
1342      * @param event event to be asserted
1343      * @param datasetId dataset set id expected in the event
1344      * @param key the only key expected in the client state bundle
1345      * @param value the only value expected in the client state bundle
1346      * @param uiType the expected ui presentation type
1347      */
assertFillEventForDatasetAuthenticationSelected( @onNull FillEventHistory.Event event, @Nullable String datasetId, @NonNull String key, @NonNull String value, int uiType)1348     public static void assertFillEventForDatasetAuthenticationSelected(
1349             @NonNull FillEventHistory.Event event,
1350             @Nullable String datasetId, @NonNull String key, @NonNull String value, int uiType) {
1351         assertFillEvent(event, TYPE_DATASET_AUTHENTICATION_SELECTED, datasetId, key, value, null);
1352         assertFillEventPresentationType(event, uiType);
1353     }
1354 
1355     /**
1356      * Asserts the content of a
1357      * {@link android.service.autofill.FillEventHistory.Event#TYPE_AUTHENTICATION_SELECTED} event.
1358      *
1359      * @param event event to be asserted
1360      * @param datasetId dataset set id expected in the event
1361      * @param key the only key expected in the client state bundle
1362      * @param value the only value expected in the client state bundle
1363      * @param uiType the expected ui presentation type
1364      */
assertFillEventForAuthenticationSelected( @onNull FillEventHistory.Event event, @Nullable String datasetId, @NonNull String key, @NonNull String value, int uiType)1365     public static void assertFillEventForAuthenticationSelected(
1366             @NonNull FillEventHistory.Event event,
1367             @Nullable String datasetId, @NonNull String key, @NonNull String value, int uiType) {
1368         assertFillEvent(event, TYPE_AUTHENTICATION_SELECTED, datasetId, key, value, null);
1369         assertFillEventPresentationType(event, uiType);
1370     }
1371 
assertFillEventForFieldsClassification(@onNull FillEventHistory.Event event, @NonNull AutofillId fieldId, @NonNull String categoryId, float score)1372     public static void assertFillEventForFieldsClassification(@NonNull FillEventHistory.Event event,
1373             @NonNull AutofillId fieldId, @NonNull String categoryId, float score) {
1374         assertFillEvent(event, TYPE_CONTEXT_COMMITTED, null, null, null,
1375                 new FieldClassificationResult[] {
1376                         new FieldClassificationResult(fieldId, categoryId, score)
1377                 });
1378     }
1379 
assertFillEventForFieldsClassification(@onNull FillEventHistory.Event event, @NonNull FieldClassificationResult[] results)1380     public static void assertFillEventForFieldsClassification(@NonNull FillEventHistory.Event event,
1381             @NonNull FieldClassificationResult[] results) {
1382         assertFillEvent(event, TYPE_CONTEXT_COMMITTED, null, null, null, results);
1383     }
1384 
assertFillEventForContextCommitted(@onNull FillEventHistory.Event event)1385     public static void assertFillEventForContextCommitted(@NonNull FillEventHistory.Event event) {
1386         assertFillEvent(event, TYPE_CONTEXT_COMMITTED, null, null, null, null);
1387     }
1388 
assertShownAndSelectedHaveSameFocusedId( @onNull FillEventHistory.Event shownEvent, @NonNull FillEventHistory.Event selectionEvent)1389     public static void assertShownAndSelectedHaveSameFocusedId(
1390             @NonNull FillEventHistory.Event shownEvent,
1391             @NonNull FillEventHistory.Event selectionEvent) {
1392         assertWithMessage("Not a shown event")
1393                 .that(shownEvent.getType())
1394                 .isEqualTo(TYPE_DATASETS_SHOWN);
1395         assertWithMessage("Not a selection event")
1396                 .that(selectionEvent.getType())
1397                 .isEqualTo(TYPE_DATASET_AUTHENTICATION_SELECTED);
1398         assertWithMessage("Shown event has no focused id")
1399                 .that(shownEvent.getFocusedId())
1400                 .isNotNull();
1401         assertWithMessage("Selection event has no focused id")
1402                 .that(selectionEvent.getFocusedId())
1403                 .isNotNull();
1404         assertWithMessage("Shown and selected Events don't have the same focused id")
1405                 .that(shownEvent.getFocusedId())
1406                 .isEqualTo(selectionEvent.getFocusedId());
1407     }
1408 
assertShownAndSelectedHaveDifferentFocusedId( @onNull FillEventHistory.Event shownEvent, @NonNull FillEventHistory.Event selectionEvent)1409     public static void assertShownAndSelectedHaveDifferentFocusedId(
1410             @NonNull FillEventHistory.Event shownEvent,
1411             @NonNull FillEventHistory.Event selectionEvent) {
1412         assertWithMessage("Not a shown event")
1413                 .that(shownEvent.getType())
1414                 .isEqualTo(TYPE_DATASETS_SHOWN);
1415         assertWithMessage("Not a selection event")
1416                 .that(selectionEvent.getType())
1417                 .isEqualTo(TYPE_DATASET_AUTHENTICATION_SELECTED);
1418         assertWithMessage("Shown event has no focused id")
1419                 .that(shownEvent.getFocusedId())
1420                 .isNotNull();
1421         assertWithMessage("Selection event has no focused id")
1422                 .that(selectionEvent.getFocusedId())
1423                 .isNotNull();
1424         assertWithMessage("Shown and selected Events don't have the same focused id")
1425                 .that(shownEvent.getFocusedId())
1426                 .isNotEqualTo(selectionEvent.getFocusedId());
1427     }
1428 
assertShownAndViewEnteredHaveSameFocusedId( @onNull FillEventHistory.Event shownEvent, @NonNull FillEventHistory.Event viewEnteredEvent)1429     public static void assertShownAndViewEnteredHaveSameFocusedId(
1430             @NonNull FillEventHistory.Event shownEvent,
1431             @NonNull FillEventHistory.Event viewEnteredEvent) {
1432         assertWithMessage("Not a shown event")
1433                 .that(shownEvent.getType())
1434                 .isEqualTo(TYPE_DATASETS_SHOWN);
1435         assertWithMessage("Not a notify view entered event")
1436                 .that(viewEnteredEvent.getType())
1437                 .isEqualTo(TYPE_VIEW_REQUESTED_AUTOFILL);
1438         assertWithMessage("Shown event has no focused id")
1439                 .that(shownEvent.getFocusedId())
1440                 .isNotNull();
1441         assertWithMessage("Notify view entered event has no focused id")
1442                 .that(viewEnteredEvent.getFocusedId())
1443                 .isNotNull();
1444         assertWithMessage("Shown and notify view entered Events don't have the same focused id")
1445                 .that(shownEvent.getFocusedId())
1446                 .isEqualTo(viewEnteredEvent.getFocusedId());
1447     }
1448 
1449     @NonNull
getActivityName(List<FillContext> contexts)1450     public static String getActivityName(List<FillContext> contexts) {
1451         if (contexts == null) return "N/A (null contexts)";
1452 
1453         if (contexts.isEmpty()) return "N/A (empty contexts)";
1454 
1455         final AssistStructure structure = contexts.get(contexts.size() - 1).getStructure();
1456         if (structure == null) return "N/A (no AssistStructure)";
1457 
1458         final ComponentName componentName = structure.getActivityComponent();
1459         if (componentName == null) return "N/A (no component name)";
1460 
1461         return componentName.flattenToShortString();
1462     }
1463 
assertFloat(float actualValue, float expectedValue)1464     public static void assertFloat(float actualValue, float expectedValue) {
1465         assertThat(actualValue).isWithin(1.0e-10f).of(expectedValue);
1466     }
1467 
assertHasFlags(int actualFlags, int expectedFlags)1468     public static void assertHasFlags(int actualFlags, int expectedFlags) {
1469         assertWithMessage("Flags %s not in %s", expectedFlags, actualFlags)
1470                 .that(actualFlags & expectedFlags).isEqualTo(expectedFlags);
1471     }
1472 
assertNoFlags(int actualFlags, int expectedFlags)1473     public static void assertNoFlags(int actualFlags, int expectedFlags) {
1474         assertWithMessage("Flags %s in %s", expectedFlags, actualFlags)
1475                 .that(actualFlags & expectedFlags).isEqualTo(0);
1476     }
1477 
callbackEventAsString(int event)1478     public static String callbackEventAsString(int event) {
1479         switch (event) {
1480             case AutofillCallback.EVENT_INPUT_HIDDEN:
1481                 return "HIDDEN";
1482             case AutofillCallback.EVENT_INPUT_SHOWN:
1483                 return "SHOWN";
1484             case AutofillCallback.EVENT_INPUT_UNAVAILABLE:
1485                 return "UNAVAILABLE";
1486             default:
1487                 return "UNKNOWN:" + event;
1488         }
1489     }
1490 
importantForAutofillAsString(int mode)1491     public static String importantForAutofillAsString(int mode) {
1492         switch (mode) {
1493             case View.IMPORTANT_FOR_AUTOFILL_AUTO:
1494                 return "IMPORTANT_FOR_AUTOFILL_AUTO";
1495             case View.IMPORTANT_FOR_AUTOFILL_YES:
1496                 return "IMPORTANT_FOR_AUTOFILL_YES";
1497             case View.IMPORTANT_FOR_AUTOFILL_YES_EXCLUDE_DESCENDANTS:
1498                 return "IMPORTANT_FOR_AUTOFILL_YES_EXCLUDE_DESCENDANTS";
1499             case View.IMPORTANT_FOR_AUTOFILL_NO:
1500                 return "IMPORTANT_FOR_AUTOFILL_NO";
1501             case View.IMPORTANT_FOR_AUTOFILL_NO_EXCLUDE_DESCENDANTS:
1502                 return "IMPORTANT_FOR_AUTOFILL_NO_EXCLUDE_DESCENDANTS";
1503             default:
1504                 return "UNKNOWN:" + mode;
1505         }
1506     }
1507 
hasHint(@ullable String[] hints, @Nullable Object expectedHint)1508     public static boolean hasHint(@Nullable String[] hints, @Nullable Object expectedHint) {
1509         if (hints == null || expectedHint == null) return false;
1510         for (String actualHint : hints) {
1511             if (expectedHint.equals(actualHint)) return true;
1512         }
1513         return false;
1514     }
1515 
newClientState(String key, String value)1516     public static Bundle newClientState(String key, String value) {
1517         final Bundle clientState = new Bundle();
1518         clientState.putString(key, value);
1519         return clientState;
1520     }
1521 
assertAuthenticationClientState(String where, Bundle data, String expectedKey, String expectedValue)1522     public static void assertAuthenticationClientState(String where, Bundle data,
1523             String expectedKey, String expectedValue) {
1524         assertWithMessage("no client state on %s", where).that(data).isNotNull();
1525         final String extraValue = data.getString(expectedKey);
1526         assertWithMessage("invalid value for %s on %s", expectedKey, where)
1527                 .that(extraValue).isEqualTo(expectedValue);
1528     }
1529 
1530     /**
1531      * Asserts that 2 bitmaps have are the same. If they aren't throws an exception and dump them
1532      * locally so their can be visually inspected.
1533      *
1534      * @param filename base name of the files generated in case of error
1535      * @param bitmap1 first bitmap to be compared
1536      * @param bitmap2 second bitmap to be compared
1537      */
1538     // TODO: move to common code
assertBitmapsAreSame(@onNull String filename, @Nullable Bitmap bitmap1, @Nullable Bitmap bitmap2)1539     public static void assertBitmapsAreSame(@NonNull String filename, @Nullable Bitmap bitmap1,
1540             @Nullable Bitmap bitmap2) throws IOException {
1541         assertWithMessage("1st bitmap is null").that(bitmap1).isNotNull();
1542         assertWithMessage("2nd bitmap is null").that(bitmap2).isNotNull();
1543         final boolean same = bitmap1.sameAs(bitmap2);
1544         if (same) {
1545             Log.v(TAG, "bitmap comparison passed for " + filename);
1546             return;
1547         }
1548 
1549         final File dir = getLocalDirectory();
1550         if (dir == null) {
1551             throw new AssertionError("bitmap comparison failed for " + filename
1552                     + ", and bitmaps could not be dumped on " + dir);
1553         }
1554         final File dump1 = dumpBitmap(bitmap1, dir, filename + "-1.png");
1555         final File dump2 = dumpBitmap(bitmap2, dir, filename + "-2.png");
1556         throw new AssertionError(
1557                 "bitmap comparison failed; check contents of " + dump1 + " and " + dump2);
1558     }
1559 
1560     @Nullable
getLocalDirectory()1561     private static File getLocalDirectory() {
1562         final File dir = new File(LOCAL_DIRECTORY);
1563         dir.mkdirs();
1564         if (!dir.exists()) {
1565             Log.e(TAG, "Could not create directory " + dir);
1566             return null;
1567         }
1568         return dir;
1569     }
1570 
1571     @Nullable
createFile(@onNull File dir, @NonNull String filename)1572     private static File createFile(@NonNull File dir, @NonNull String filename) throws IOException {
1573         final File file = new File(dir, filename);
1574         if (file.exists()) {
1575             Log.v(TAG, "Deleting file " + file);
1576             file.delete();
1577         }
1578         if (!file.createNewFile()) {
1579             Log.e(TAG, "Could not create file " + file);
1580             return null;
1581         }
1582         return file;
1583     }
1584 
1585     @Nullable
dumpBitmap(@onNull Bitmap bitmap, @NonNull File dir, @NonNull String filename)1586     private static File dumpBitmap(@NonNull Bitmap bitmap, @NonNull File dir,
1587             @NonNull String filename) throws IOException {
1588         final File file = createFile(dir, filename);
1589         if (file != null) {
1590             dumpBitmap(bitmap, file);
1591 
1592         }
1593         return file;
1594     }
1595 
1596     @Nullable
dumpBitmap(@onNull Bitmap bitmap, @NonNull File file)1597     public static File dumpBitmap(@NonNull Bitmap bitmap, @NonNull File file) {
1598         Log.i(TAG, "Dumping bitmap at " + file);
1599         BitmapUtils.saveBitmap(bitmap, file.getParent(), file.getName());
1600         return file;
1601     }
1602 
1603     /**
1604      * Creates a file in the device, using the name of the current test as a prefix.
1605      */
1606     @Nullable
createTestFile(@onNull String name)1607     public static File createTestFile(@NonNull String name) throws IOException {
1608         final File dir = getLocalDirectory();
1609         if (dir == null) return null;
1610 
1611         final String prefix = TestNameUtils.getCurrentTestName().replaceAll("\\.|\\(|\\/", "_")
1612                 .replaceAll("\\)", "");
1613         final String filename = prefix + "-" + name;
1614 
1615         return createFile(dir, filename);
1616     }
1617 
1618     /**
1619      * Offers an object to a queue or times out.
1620      *
1621      * @return {@code true} if the offer was accepted, {$code false} if it timed out or was
1622      * interrupted.
1623      */
offer(BlockingQueue<T> queue, T obj, long timeoutMs)1624     public static <T> boolean offer(BlockingQueue<T> queue, T obj, long timeoutMs) {
1625         boolean offered = false;
1626         try {
1627             offered = queue.offer(obj, timeoutMs, TimeUnit.MILLISECONDS);
1628         } catch (InterruptedException e) {
1629             Log.w(TAG, "interrupted offering", e);
1630             Thread.currentThread().interrupt();
1631         }
1632         if (!offered) {
1633             Log.e(TAG, "could not offer " + obj + " in " + timeoutMs + "ms");
1634         }
1635         return offered;
1636     }
1637 
1638     /**
1639      * Calls this method to assert given {@code string} is equal to {@link #LARGE_STRING}, as
1640      * comparing its value using standard assertions might ANR.
1641      */
assertEqualsToLargeString(@onNull String string)1642     public static void assertEqualsToLargeString(@NonNull String string) {
1643         assertThat(string).isNotNull();
1644         assertThat(string).hasLength(LARGE_STRING_SIZE);
1645         assertThat(string.charAt(0)).isEqualTo(LARGE_STRING_CHAR);
1646         assertThat(string.charAt(LARGE_STRING_SIZE - 1)).isEqualTo(LARGE_STRING_CHAR);
1647     }
1648 
1649     /**
1650      * Asserts that autofill is enabled in the context, retrying if necessariy.
1651      */
assertAutofillEnabled(@onNull Context context, boolean expected)1652     public static void assertAutofillEnabled(@NonNull Context context, boolean expected)
1653             throws Exception {
1654         assertAutofillEnabled(context.getSystemService(AutofillManager.class), expected);
1655     }
1656 
1657     /**
1658      * Asserts that autofill is enabled in the manager, retrying if necessariy.
1659      */
assertAutofillEnabled(@onNull AutofillManager afm, boolean expected)1660     public static void assertAutofillEnabled(@NonNull AutofillManager afm, boolean expected)
1661             throws Exception {
1662         Timeouts.IDLE_UNBIND_TIMEOUT.run("assertEnabled(" + expected + ")", () -> {
1663             final boolean actual = afm.isEnabled();
1664             Log.v(TAG, "assertEnabled(): expected=" + expected + ", actual=" + actual);
1665             return actual == expected ? "not_used" : null;
1666         });
1667     }
1668 
1669     /**
1670      * Asserts these autofill ids are the same, except for the session.
1671      */
assertEqualsIgnoreSession(@onNull AutofillId id1, @NonNull AutofillId id2)1672     public static void assertEqualsIgnoreSession(@NonNull AutofillId id1, @NonNull AutofillId id2) {
1673         assertWithMessage("id1 is null").that(id1).isNotNull();
1674         assertWithMessage("id2 is null").that(id2).isNotNull();
1675         assertWithMessage("%s is not equal to %s", id1, id2).that(id1.equalsIgnoreSession(id2))
1676                 .isTrue();
1677     }
1678 
1679     /**
1680      * Asserts {@link View#isAutofilled()} state of the given view, waiting if necessarity to avoid
1681      * race conditions.
1682      */
assertViewAutofillState(@onNull View view, boolean expected)1683     public static void assertViewAutofillState(@NonNull View view, boolean expected)
1684             throws Exception {
1685         Timeouts.FILL_TIMEOUT.run("assertViewAutofillState(" + view + ", " + expected + ")",
1686                 () -> {
1687                     final boolean actual = view.isAutofilled();
1688                     Log.v(TAG, "assertViewAutofillState(): expected=" + expected + ", actual="
1689                             + actual);
1690                     return actual == expected ? "not_used" : null;
1691                 });
1692     }
1693 
1694     /**
1695      * Allows the test to draw overlaid windows.
1696      *
1697      * <p>Should call {@link #disallowOverlays()} afterwards.
1698      */
allowOverlays()1699     public static void allowOverlays() {
1700         ShellUtils.setOverlayPermissions(MY_PACKAGE, true);
1701     }
1702 
1703     /**
1704      * Disallow the test to draw overlaid windows.
1705      *
1706      * <p>Should call {@link #disallowOverlays()} afterwards.
1707      */
disallowOverlays()1708     public static void disallowOverlays() {
1709         ShellUtils.setOverlayPermissions(MY_PACKAGE, false);
1710     }
1711 
createPresentation(String message)1712     public static RemoteViews createPresentation(String message) {
1713         final RemoteViews presentation = new RemoteViews(getContext()
1714                 .getPackageName(), R.layout.list_item);
1715         presentation.setTextViewText(R.id.text1, message);
1716         return presentation;
1717     }
1718 
createInlinePresentation(String message)1719     public static InlinePresentation createInlinePresentation(String message) {
1720         final PendingIntent dummyIntent = PendingIntent.getActivity(getContext(), 0, new Intent(),
1721                 PendingIntent.FLAG_IMMUTABLE);
1722         return createInlinePresentation(message, dummyIntent, false);
1723     }
1724 
createInlinePresentation(String message, PendingIntent attribution)1725     public static InlinePresentation createInlinePresentation(String message,
1726             PendingIntent attribution) {
1727         return createInlinePresentation(message, attribution, false);
1728     }
1729 
createPinnedInlinePresentation(String message)1730     public static InlinePresentation createPinnedInlinePresentation(String message) {
1731         final PendingIntent dummyIntent = PendingIntent.getActivity(getContext(), 0, new Intent(),
1732                 PendingIntent.FLAG_IMMUTABLE);
1733         return createInlinePresentation(message, dummyIntent, true);
1734     }
1735 
createInlinePresentation(@onNull String message, @NonNull PendingIntent attribution, boolean pinned)1736     private static InlinePresentation createInlinePresentation(@NonNull String message,
1737             @NonNull PendingIntent attribution, boolean pinned) {
1738         return new InlinePresentation(
1739                 InlineSuggestionUi.newContentBuilder(attribution)
1740                         .setTitle(message).build().getSlice(),
1741                 new InlinePresentationSpec.Builder(new Size(100, 100), new Size(400, 100))
1742                         .build(), /* pinned= */ pinned);
1743     }
1744 
createInlineTooltipPresentation( @onNull String message)1745     public static InlinePresentation createInlineTooltipPresentation(
1746             @NonNull String message) {
1747         final PendingIntent dummyIntent = PendingIntent.getActivity(getContext(), 0, new Intent(),
1748                 PendingIntent.FLAG_IMMUTABLE);
1749         return createInlineTooltipPresentation(message, dummyIntent);
1750     }
1751 
createInlineTooltipPresentation( @onNull String message, @NonNull PendingIntent attribution)1752     private static InlinePresentation createInlineTooltipPresentation(
1753             @NonNull String message, @NonNull PendingIntent attribution) {
1754         return InlinePresentation.createTooltipPresentation(
1755                 InlineSuggestionUi.newContentBuilder(attribution)
1756                         .setTitle(message).build().getSlice(),
1757                 new InlinePresentationSpec.Builder(new Size(100, 100), new Size(400, 100))
1758                         .build());
1759     }
1760 
mockSwitchInputMethod(@onNull Context context)1761     public static void mockSwitchInputMethod(@NonNull Context context) throws Exception {
1762         final ContentResolver cr = context.getContentResolver();
1763         final int subtype = Settings.Secure.getInt(cr, SELECTED_INPUT_METHOD_SUBTYPE);
1764         Settings.Secure.putInt(cr, SELECTED_INPUT_METHOD_SUBTYPE, subtype);
1765     }
1766 
1767     /**
1768      * Reset AutofillOptions to avoid cts package was added to augmented autofill allowlist.
1769      */
resetApplicationAutofillOptions(@onNull Context context)1770     public static void resetApplicationAutofillOptions(@NonNull Context context) {
1771         AutofillOptions options = AutofillOptions.forWhitelistingItself();
1772         options.augmentedAutofillEnabled = false;
1773         context.getApplicationContext().setAutofillOptions(options);
1774     }
1775 
1776     /**
1777      * Clear AutofillOptions.
1778      */
clearApplicationAutofillOptions(@onNull Context context)1779     public static void clearApplicationAutofillOptions(@NonNull Context context) {
1780         context.getApplicationContext().setAutofillOptions(null);
1781     }
1782 
1783     /**
1784      * Set device config to set flag values.
1785      */
setDeviceConfig( @onNull Context context, @NonNull String feature, boolean value)1786     public static void setDeviceConfig(
1787             @NonNull Context context, @NonNull String feature, boolean value) {
1788         DeviceConfigStateManager deviceConfigStateManager =
1789                 new DeviceConfigStateManager(context, DeviceConfig.NAMESPACE_AUTOFILL, feature);
1790         setDeviceConfig(deviceConfigStateManager, String.valueOf(value));
1791     }
1792 
1793     /**
1794      * Enable fill dialog feature
1795      */
enableFillDialogFeature(@onNull Context context)1796     public static void enableFillDialogFeature(@NonNull Context context) {
1797         DeviceConfigStateManager deviceConfigStateManager =
1798                 new DeviceConfigStateManager(context, DeviceConfig.NAMESPACE_AUTOFILL,
1799                         AutofillFeatureFlags.DEVICE_CONFIG_AUTOFILL_DIALOG_ENABLED);
1800         setDeviceConfig(deviceConfigStateManager, "true");
1801     }
1802 
1803     /**
1804      * Enable fill dialog feature
1805      */
disableFillDialogFeature(@onNull Context context)1806     public static void disableFillDialogFeature(@NonNull Context context) {
1807         DeviceConfigStateManager deviceConfigStateManager =
1808                 new DeviceConfigStateManager(context, DeviceConfig.NAMESPACE_AUTOFILL,
1809                         AutofillFeatureFlags.DEVICE_CONFIG_AUTOFILL_DIALOG_ENABLED);
1810         setDeviceConfig(deviceConfigStateManager, "false");
1811     }
1812 
1813     /**
1814      * Enable fill dialog improvements
1815      */
enableFillDialogImprovements(@onNull Context context)1816     public static void enableFillDialogImprovements(@NonNull Context context) {
1817         enableFillDialogFeature(context);
1818         DeviceConfigStateManager deviceConfigStateManager =
1819                 new DeviceConfigStateManager(context, DeviceConfig.NAMESPACE_AUTOFILL,
1820                         "improve_fill_dialog");
1821         setDeviceConfig(deviceConfigStateManager, "true");
1822     }
1823 
1824     /**
1825      * Disable fill dialog improvements
1826      */
disableFillDialogImprovements(@onNull Context context)1827     public static void disableFillDialogImprovements(@NonNull Context context) {
1828         disableFillDialogFeature(context);
1829         DeviceConfigStateManager deviceConfigStateManager =
1830                 new DeviceConfigStateManager(context, DeviceConfig.NAMESPACE_AUTOFILL,
1831                         "improve_fill_dialog");
1832         setDeviceConfig(deviceConfigStateManager, "false");
1833     }
1834 
1835     /**
1836      * Enable PCC Detection Feature Hints
1837      */
enablePccDetectionFeature(@onNull Context context, String...types)1838     public static void enablePccDetectionFeature(@NonNull Context context, String...types) {
1839         DeviceConfigStateManager deviceConfigStateManager =
1840                 new DeviceConfigStateManager(context, DeviceConfig.NAMESPACE_AUTOFILL,
1841                         AutofillFeatureFlags.DEVICE_CONFIG_AUTOFILL_PCC_FEATURE_PROVIDER_HINTS);
1842         setDeviceConfig(deviceConfigStateManager, TextUtils.join(",", types));
1843 
1844         DeviceConfigStateManager deviceConfigStateManager2 =
1845                 new DeviceConfigStateManager(context, DeviceConfig.NAMESPACE_AUTOFILL,
1846                         AutofillFeatureFlags.DEVICE_CONFIG_AUTOFILL_PCC_CLASSIFICATION_ENABLED);
1847         setDeviceConfig(deviceConfigStateManager2, "true");
1848     }
1849 
1850     /**
1851      * Enable PCC Detection Feature Hints
1852      */
preferPccDetectionOverProvider(@onNull Context context, boolean preferPcc)1853     public static void preferPccDetectionOverProvider(@NonNull Context context, boolean preferPcc) {
1854         DeviceConfigStateManager deviceConfigStateManager =
1855                 new DeviceConfigStateManager(context, DeviceConfig.NAMESPACE_AUTOFILL,
1856                         "prefer_provider_over_pcc");
1857         setDeviceConfig(deviceConfigStateManager, String.valueOf(!preferPcc));
1858     }
1859 
1860     /**
1861      * Disable PCC Detection Feature
1862      */
disablePccDetectionFeature(@onNull Context context)1863     public static void disablePccDetectionFeature(@NonNull Context context) {
1864         DeviceConfigStateManager deviceConfigStateManager2 =
1865                 new DeviceConfigStateManager(context, DeviceConfig.NAMESPACE_AUTOFILL,
1866                         AutofillFeatureFlags.DEVICE_CONFIG_AUTOFILL_PCC_CLASSIFICATION_ENABLED);
1867         setDeviceConfig(deviceConfigStateManager2, "false");
1868     }
1869 
1870     /**
1871      * Set hints list for fill dialog
1872      */
setFillDialogHints(@onNull Context context, @Nullable String hints)1873     public static void setFillDialogHints(@NonNull Context context, @Nullable String hints) {
1874         DeviceConfigStateManager deviceConfigStateManager =
1875                 new DeviceConfigStateManager(context, DeviceConfig.NAMESPACE_AUTOFILL,
1876                         DEVICE_CONFIG_AUTOFILL_DIALOG_HINTS);
1877         setDeviceConfig(deviceConfigStateManager, hints);
1878     }
1879 
1880     /**
1881      * Disable relayout fix
1882      */
disableRelayoutFix(@onNull Context context)1883     public static void disableRelayoutFix(@NonNull Context context) {
1884         DeviceConfigStateManager deviceConfigStateManager = new DeviceConfigStateManager(
1885                 context, DeviceConfig.NAMESPACE_AUTOFILL, "enable_relayout");
1886         setDeviceConfig(deviceConfigStateManager, "false");
1887     }
1888 
1889     /**
1890      * Enable relayout fix
1891      */
enableRelayoutFix(@onNull Context context)1892     public static void enableRelayoutFix(@NonNull Context context) {
1893         DeviceConfigStateManager deviceConfigStateManager = new DeviceConfigStateManager(
1894                 context, DeviceConfig.NAMESPACE_AUTOFILL, "enable_relayout");
1895         setDeviceConfig(deviceConfigStateManager, "true");
1896     }
1897 
setDeviceConfig(@onNull DeviceConfigStateManager deviceConfigStateManager, @Nullable String value)1898     public static void setDeviceConfig(@NonNull DeviceConfigStateManager deviceConfigStateManager,
1899             @Nullable String value) {
1900         final String previousValue = deviceConfigStateManager.get();
1901         if (TextUtils.isEmpty(value) && TextUtils.isEmpty(previousValue)
1902                 || TextUtils.equals(previousValue, value)) {
1903             Log.v(TAG, "No changed in config: " + deviceConfigStateManager);
1904             return;
1905         }
1906 
1907         deviceConfigStateManager.set(value);
1908     }
1909 
isPccFieldClassificationSet(@onNull Context context)1910     public static boolean isPccFieldClassificationSet(@NonNull Context context) {
1911         return Boolean.valueOf(runShellCommand(
1912                 "cmd autofill get field-detection-service-enabled " + context.getUserId()));
1913     }
1914 
1915     /**
1916      * Whether IME is showing
1917      */
isImeShowing(WindowInsets rootWindowInsets)1918     public static boolean isImeShowing(WindowInsets rootWindowInsets) {
1919         if (rootWindowInsets != null && rootWindowInsets.isVisible(WindowInsets.Type.ime())) {
1920             return true;
1921         }
1922         return false;
1923     }
1924 
getSystemResourceId(String id, String type, String packageName)1925     public static int getSystemResourceId(String id, String type, String packageName) {
1926         return Resources.getSystem().getIdentifier(id, type, packageName);
1927     }
1928 
1929     /**
1930      * Asserts whether mock IME is showing
1931      */
assertMockImeStatus(AbstractAutoFillActivity activity, boolean expectedImeShow)1932     public static void assertMockImeStatus(AbstractAutoFillActivity activity,
1933             boolean expectedImeShow) throws Exception {
1934         Timeouts.MOCK_IME_TIMEOUT.run("assertMockImeStatus(" + expectedImeShow + ")",
1935                 () -> {
1936                     final boolean actual = isImeShowing(activity.getRootWindowInsets());
1937                     Log.v(TAG, "assertMockImeStatus(): expected=" + expectedImeShow + ", actual="
1938                             + actual);
1939                     return actual == expectedImeShow ? "expected" : null;
1940                 });
1941     }
1942 
1943     /**
1944      * Make sure the activity that the name is clazz resumed.
1945      */
assertActivityShownInBackground(Class<?> clazz)1946     public static void assertActivityShownInBackground(Class<?> clazz) throws Exception {
1947         Timeouts.UI_TIMEOUT.run("activity is not resumed: " + clazz, () -> {
1948             ArrayList<Boolean> result = new ArrayList<>();
1949             InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
1950                 final Collection<Activity> stage = ActivityLifecycleMonitorRegistry.getInstance()
1951                         .getActivitiesInStage(Stage.RESUMED);
1952                 for (Activity act : stage) {
1953                     if (act.getClass().equals(clazz)) {
1954                         result.add(Boolean.TRUE);
1955                     }
1956                 }
1957             });
1958             return result.isEmpty() ? null : Boolean.TRUE;
1959         });
1960     }
1961 
1962     /**
1963      * Whether the device is TV.
1964      * @param context
1965      * @return true if the device is TV, false otherwise
1966      */
isTv(Context context)1967     public static boolean isTv(Context context) {
1968         PackageManager pm = context.getPackageManager();
1969         return pm.hasSystemFeature(PackageManager.FEATURE_LEANBACK)
1970                 || pm.hasSystemFeature(PackageManager.FEATURE_TELEVISION);
1971     }
1972 
Helper()1973     private Helper() {
1974         throw new UnsupportedOperationException("contain static methods only");
1975     }
1976 
1977     public enum DeviceStateEnum {
1978         HALF_FOLDED,
1979         OPENED,
1980         REAR_DISPLAY
1981     };
1982 
1983     /**
1984      * Test if the device is in half-folded or rear display state.
1985      */
1986     private static final class DeviceStateAssessor implements DeviceStateCallback {
1987         DeviceStateManager mDeviceStateManager;
1988         int[] mHalfFoldedStates;
1989         int[] mRearDisplayStates;
1990         int[] mOpenDisplayStates;
1991         int mCurrentState = -1;
1992 
DeviceStateAssessor(Context context)1993         DeviceStateAssessor(Context context) {
1994             Resources systemRes = Resources.getSystem();
1995             mHalfFoldedStates = getStatesFromConfig(systemRes, "config_halfFoldedDeviceStates");
1996             mRearDisplayStates = getStatesFromConfig(systemRes, "config_rearDisplayDeviceStates");
1997             mOpenDisplayStates = getStatesFromConfig(systemRes, "config_openDeviceStates");
1998             try {
1999                 mDeviceStateManager = context.getSystemService(DeviceStateManager.class);
2000                 mDeviceStateManager.registerCallback(context.getMainExecutor(), this);
2001                 Log.v(TAG, "DeviceStateAssessor initialized halfFoldedStates.length="
2002                         + mHalfFoldedStates.length + ", readDisplayStates.length="
2003                         + mRearDisplayStates.length);
2004             } catch (java.lang.IllegalStateException e) {
2005                 Log.v(TAG, "DeviceStateManager not available: cannot check for half-fold");
2006             }
2007         }
2008 
getStatesFromConfig(Resources systemRes, String configKey)2009         private int[] getStatesFromConfig(Resources systemRes, String configKey) {
2010             int statesArrayIdentifier = systemRes.getIdentifier(configKey, "array", "android");
2011             if (statesArrayIdentifier == 0) {
2012                 return new int[0];
2013             } else {
2014                 return systemRes.getIntArray(statesArrayIdentifier);
2015             }
2016         }
2017 
2018         @Override
onDeviceStateChanged(DeviceState state)2019         public void onDeviceStateChanged(DeviceState state) {
2020             synchronized (this) {
2021                 mCurrentState = state.getIdentifier();
2022                 this.notify();
2023             }
2024         }
2025 
close()2026         void close() {
2027             if (mDeviceStateManager != null) {
2028                 mDeviceStateManager.unregisterCallback(this);
2029             }
2030         }
2031 
isDeviceInState(DeviceStateEnum deviceState)2032         boolean isDeviceInState(DeviceStateEnum deviceState) throws InterruptedException {
2033             int[] states;
2034             switch(deviceState) {
2035                 case HALF_FOLDED:
2036                     states = mHalfFoldedStates;
2037                     break;
2038                 case OPENED:
2039                     states = mOpenDisplayStates;
2040                     break;
2041                 case REAR_DISPLAY:
2042                     states = mRearDisplayStates;
2043                     break;
2044                 default:
2045                     return false;
2046             }
2047             if (states.length == 0 || mDeviceStateManager == null) {
2048                 return false;
2049             }
2050             synchronized (this) {
2051                 if (mCurrentState == -1) {
2052                     this.wait(1000);
2053                 }
2054             }
2055             if (mCurrentState == -1) {
2056                 Log.w(TAG, "DeviceStateCallback not called within 1 second");
2057             }
2058             Log.v(TAG, "Current state=" + mCurrentState + ", states[0]="
2059                     + states[0]);
2060             return Arrays.stream(states).anyMatch(x -> x == mCurrentState);
2061         }
2062     }
2063 
isDeviceInState(Context context, DeviceStateEnum deviceState)2064     public static boolean isDeviceInState(Context context, DeviceStateEnum deviceState) {
2065         DeviceStateAssessor deviceStateAssessor = new DeviceStateAssessor(context);
2066         try {
2067             return deviceStateAssessor.isDeviceInState(deviceState);
2068         } catch (InterruptedException e) {
2069             return false;
2070         } finally {
2071             deviceStateAssessor.close();
2072         }
2073     }
2074 
2075     public static class CustomDescriptionUtils {
newTemplate(String packageName)2076         public static RemoteViews newTemplate(String packageName) {
2077             return new RemoteViews(packageName, R.layout.custom_description_with_link);
2078         }
2079 
newCustomDescriptionBuilder( Context context, Class<? extends Activity> activityClass, String packageName)2080         public static final CustomDescription.Builder newCustomDescriptionBuilder(
2081                 Context context, Class<? extends Activity> activityClass, String packageName) {
2082             final Intent intent = new Intent(context, activityClass);
2083             intent.setFlags(
2084                     Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS | Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
2085             return newCustomDescriptionBuilder(context, intent, packageName);
2086         }
2087 
newCustomDescription( Context context, Class<? extends Activity> activityClass, String packageName)2088         public static final CustomDescription newCustomDescription(
2089                 Context context, Class<? extends Activity> activityClass, String packageName) {
2090             return newCustomDescriptionBuilder(context, activityClass, packageName).build();
2091         }
2092 
newCustomDescriptionBuilder( Context context, Intent intent, String packageName)2093         public static final CustomDescription.Builder newCustomDescriptionBuilder(
2094                 Context context, Intent intent, String packageName) {
2095             final RemoteViews presentation = newTemplate(packageName);
2096             final PendingIntent pendingIntent =
2097                     PendingIntent.getActivity(
2098                             context,
2099                             0,
2100                             intent,
2101                             PendingIntent.FLAG_MUTABLE,
2102                             ActivityOptions.makeBasic()
2103                                     .setPendingIntentCreatorBackgroundActivityStartMode(
2104                                             ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED)
2105                                     .toBundle());
2106             presentation.setOnClickPendingIntent(R.id.link, pendingIntent);
2107             return new CustomDescription.Builder(presentation);
2108         }
2109 
newCustomDescription( Context context, Intent intent, String packageName)2110         public static final CustomDescription newCustomDescription(
2111                 Context context, Intent intent, String packageName) {
2112             return newCustomDescriptionBuilder(context, intent, packageName).build();
2113         }
2114     }
2115 
2116     public static class FieldClassificationResult {
2117         public final AutofillId id;
2118         public final String[] categoryIds;
2119         public final float[] scores;
2120 
FieldClassificationResult(@onNull AutofillId id, @NonNull String categoryId, float score)2121         public FieldClassificationResult(@NonNull AutofillId id, @NonNull String categoryId,
2122                 float score) {
2123             this(id, new String[]{categoryId}, new float[]{score});
2124         }
2125 
FieldClassificationResult(@onNull AutofillId id, @NonNull String[] categoryIds, float[] scores)2126         public FieldClassificationResult(@NonNull AutofillId id, @NonNull String[] categoryIds,
2127                 float[] scores) {
2128             this.id = id;
2129             this.categoryIds = categoryIds;
2130             this.scores = scores;
2131         }
2132     }
2133 
assertHasEventMatchingTypeAndFilter(int type, Consumer<Event> f, List<Event> events)2134     public static void assertHasEventMatchingTypeAndFilter(int type,
2135             Consumer<Event> f, List<Event> events) {
2136         boolean eventFound = false;
2137         for (Event event : events) {
2138             if (event.getType() == type) {
2139                 f.accept(event);
2140                 eventFound = true;
2141                 break;
2142             }
2143         }
2144         assertThat(eventFound).isTrue();
2145     }
2146 }
2147