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