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