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