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