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.InstrumentedAutoFillService.SERVICE_NAME;
20 import static android.autofillservice.cts.UiBot.PORTRAIT;
21 import static android.autofillservice.cts.common.ShellHelper.runShellCommand;
22 import static android.provider.Settings.Secure.AUTOFILL_SERVICE;
23 import static android.provider.Settings.Secure.USER_SETUP_COMPLETE;
24 import static android.service.autofill.FillEventHistory.Event.TYPE_AUTHENTICATION_SELECTED;
25 import static android.service.autofill.FillEventHistory.Event.TYPE_CONTEXT_COMMITTED;
26 import static android.service.autofill.FillEventHistory.Event.TYPE_DATASET_AUTHENTICATION_SELECTED;
27 import static android.service.autofill.FillEventHistory.Event.TYPE_DATASET_SELECTED;
28 import static android.service.autofill.FillEventHistory.Event.TYPE_SAVE_SHOWN;
29 
30 import static com.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.autofillservice.cts.common.SettingsHelper;
38 import android.content.ComponentName;
39 import android.content.Context;
40 import android.content.pm.PackageManager;
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.support.test.InstrumentationRegistry;
50 import android.text.TextUtils;
51 import android.util.Log;
52 import android.util.Pair;
53 import android.view.View;
54 import android.view.ViewGroup;
55 import android.view.ViewStructure.HtmlInfo;
56 import android.view.autofill.AutofillId;
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 
64 import com.android.compatibility.common.util.BitmapUtils;
65 import com.android.compatibility.common.util.RequiredFeatureRule;
66 
67 import java.io.File;
68 import java.io.IOException;
69 import java.util.List;
70 import java.util.Map;
71 import java.util.Map.Entry;
72 import java.util.function.Function;
73 
74 /**
75  * Helper for common funcionalities.
76  */
77 final class Helper {
78 
79     static final String TAG = "AutoFillCtsHelper";
80 
81     static final boolean VERBOSE = false;
82 
83     static final String MY_PACKAGE = "android.autofillservice.cts";
84 
85     static final String ID_USERNAME_LABEL = "username_label";
86     static final String ID_USERNAME = "username";
87     static final String ID_PASSWORD_LABEL = "password_label";
88     static final String ID_PASSWORD = "password";
89     static final String ID_LOGIN = "login";
90     static final String ID_OUTPUT = "output";
91     static final String ID_STATIC_TEXT = "static_text";
92 
93     public static final String NULL_DATASET_ID = null;
94 
95     /**
96      * Can be used in cases where the autofill values is required by irrelevant (like adding a
97      * value to an authenticated dataset).
98      */
99     public static final String UNUSED_AUTOFILL_VALUE = null;
100 
101     private static final String CMD_LIST_SESSIONS = "cmd autofill list sessions";
102 
103     private static final String ACCELLEROMETER_CHANGE =
104             "content insert --uri content://settings/system --bind name:s:accelerometer_rotation "
105                     + "--bind value:i:%d";
106 
107     private static final String LOCAL_DIRECTORY = Environment.getExternalStorageDirectory()
108             + "/CtsAutoFillServiceTestCases";
109 
110     /**
111      * Helper interface used to filter nodes.
112      *
113      * @param <T> node type
114      */
115     interface NodeFilter<T> {
116         /**
117          * Returns whether the node passes the filter for such given id.
118          */
matches(T node, Object id)119         boolean matches(T node, Object id);
120     }
121 
122     private static final NodeFilter<ViewNode> RESOURCE_ID_FILTER = (node, id) -> {
123         return id.equals(node.getIdEntry());
124     };
125 
126     private static final NodeFilter<ViewNode> AUTOFILL_ID_FILTER = (node, id) -> {
127         return id.equals(node.getAutofillId());
128     };
129 
130     private static final NodeFilter<ViewNode> HTML_NAME_FILTER = (node, id) -> {
131         return id.equals(getHtmlName(node));
132     };
133 
134     private static final NodeFilter<ViewNode> HTML_NAME_OR_RESOURCE_ID_FILTER = (node, id) -> {
135         return id.equals(getHtmlName(node)) || id.equals(node.getIdEntry());
136     };
137 
138     private static final NodeFilter<ViewNode> TEXT_FILTER = (node, id) -> {
139         return id.equals(node.getText());
140     };
141 
142     private static final NodeFilter<ViewNode> AUTOFILL_HINT_FILTER = (node, id) -> {
143         return hasHint(node.getAutofillHints(), id);
144     };
145 
146     private static final NodeFilter<ViewNode> WEBVIEW_FORM_FILTER = (node, id) -> {
147         final String className = node.getClassName();
148         if (!className.equals("android.webkit.WebView")) return false;
149 
150         final HtmlInfo htmlInfo = assertHasHtmlTag(node, "form");
151         final String formName = getAttributeValue(htmlInfo, "name");
152         return id.equals(formName);
153     };
154 
155     private static final NodeFilter<View> AUTOFILL_HINT_VIEW_FILTER = (view, id) -> {
156         return hasHint(view.getAutofillHints(), id);
157     };
158 
159     /**
160      * Dump the assist structure on logcat.
161      */
dumpStructure(String message, AssistStructure structure)162     static void dumpStructure(String message, AssistStructure structure) {
163         final StringBuffer buffer = new StringBuffer(message)
164                 .append(": component=")
165                 .append(structure.getActivityComponent());
166         final int nodes = structure.getWindowNodeCount();
167         for (int i = 0; i < nodes; i++) {
168             final WindowNode windowNode = structure.getWindowNodeAt(i);
169             dump(buffer, windowNode.getRootViewNode(), " ", 0);
170         }
171         Log.i(TAG, buffer.toString());
172     }
173 
174     /**
175      * Dump the contexts on logcat.
176      */
dumpStructure(String message, List<FillContext> contexts)177     static void dumpStructure(String message, List<FillContext> contexts) {
178         for (FillContext context : contexts) {
179             dumpStructure(message, context.getStructure());
180         }
181     }
182 
183     /**
184      * Dumps the state of the autofill service on logcat.
185      */
dumpAutofillService()186     static void dumpAutofillService() {
187         Log.i(TAG, "dumpsys autofill\n\n" + runShellCommand("dumpsys autofill"));
188     }
189 
190     /**
191      * Sets whether the user completed the initial setup.
192      */
setUserComplete(Context context, boolean complete)193     static void setUserComplete(Context context, boolean complete) {
194         SettingsHelper.syncSet(context, USER_SETUP_COMPLETE, complete ? "1" : null);
195     }
196 
dump(StringBuffer buffer, ViewNode node, String prefix, int childId)197     private static void dump(StringBuffer buffer, ViewNode node, String prefix, int childId) {
198         final int childrenSize = node.getChildCount();
199         buffer.append("\n").append(prefix)
200             .append('#').append(childId).append(':')
201             .append("resId=").append(node.getIdEntry())
202             .append(" class=").append(node.getClassName())
203             .append(" text=").append(node.getText())
204             .append(" class=").append(node.getClassName())
205             .append(" webDomain=").append(node.getWebDomain())
206             .append(" #children=").append(childrenSize);
207 
208         buffer.append("\n").append(prefix)
209             .append("   afId=").append(node.getAutofillId())
210             .append(" afType=").append(node.getAutofillType())
211             .append(" afValue=").append(node.getAutofillValue())
212             .append(" checked=").append(node.isChecked())
213             .append(" focused=").append(node.isFocused());
214 
215         final HtmlInfo htmlInfo = node.getHtmlInfo();
216         if (htmlInfo != null) {
217             buffer.append("\nHtmlInfo: tag=").append(htmlInfo.getTag())
218                 .append(", attrs: ").append(htmlInfo.getAttributes());
219         }
220 
221         prefix += " ";
222         if (childrenSize > 0) {
223             for (int i = 0; i < childrenSize; i++) {
224                 dump(buffer, node.getChildAt(i), prefix, i);
225             }
226         }
227     }
228 
229     /**
230      * Gets a node if it matches the filter criteria for the given id.
231      */
findNodeByFilter(@onNull AssistStructure structure, @NonNull Object id, @NonNull NodeFilter<ViewNode> filter)232     static ViewNode findNodeByFilter(@NonNull AssistStructure structure, @NonNull Object id,
233             @NonNull NodeFilter<ViewNode> filter) {
234         Log.v(TAG, "Parsing request for activity " + structure.getActivityComponent());
235         final int nodes = structure.getWindowNodeCount();
236         for (int i = 0; i < nodes; i++) {
237             final WindowNode windowNode = structure.getWindowNodeAt(i);
238             final ViewNode rootNode = windowNode.getRootViewNode();
239             final ViewNode node = findNodeByFilter(rootNode, id, filter);
240             if (node != null) {
241                 return node;
242             }
243         }
244         return null;
245     }
246 
247     /**
248      * Gets a node if it matches the filter criteria for the given id.
249      */
findNodeByFilter(@onNull List<FillContext> contexts, @NonNull Object id, @NonNull NodeFilter<ViewNode> filter)250     static ViewNode findNodeByFilter(@NonNull List<FillContext> contexts, @NonNull Object id,
251             @NonNull NodeFilter<ViewNode> filter) {
252         for (FillContext context : contexts) {
253             ViewNode node = findNodeByFilter(context.getStructure(), id, filter);
254             if (node != null) {
255                 return node;
256             }
257         }
258         return null;
259     }
260 
261     /**
262      * Gets a node if it matches the filter criteria for the given id.
263      */
findNodeByFilter(@onNull ViewNode node, @NonNull Object id, @NonNull NodeFilter<ViewNode> filter)264     static ViewNode findNodeByFilter(@NonNull ViewNode node, @NonNull Object id,
265             @NonNull NodeFilter<ViewNode> filter) {
266         if (filter.matches(node, id)) {
267             return node;
268         }
269         final int childrenSize = node.getChildCount();
270         if (childrenSize > 0) {
271             for (int i = 0; i < childrenSize; i++) {
272                 final ViewNode found = findNodeByFilter(node.getChildAt(i), id, filter);
273                 if (found != null) {
274                     return found;
275                 }
276             }
277         }
278         return null;
279     }
280 
281     /**
282      * Gets a node given its Android resource id, or {@code null} if not found.
283      */
findNodeByResourceId(AssistStructure structure, String resourceId)284     static ViewNode findNodeByResourceId(AssistStructure structure, String resourceId) {
285         return findNodeByFilter(structure, resourceId, RESOURCE_ID_FILTER);
286     }
287 
288     /**
289      * Gets a node given its Android resource id, or {@code null} if not found.
290      */
findNodeByResourceId(List<FillContext> contexts, String resourceId)291     static ViewNode findNodeByResourceId(List<FillContext> contexts, String resourceId) {
292         return findNodeByFilter(contexts, resourceId, RESOURCE_ID_FILTER);
293     }
294 
295     /**
296      * Gets a node given its Android resource id, or {@code null} if not found.
297      */
findNodeByResourceId(ViewNode node, String resourceId)298     static ViewNode findNodeByResourceId(ViewNode node, String resourceId) {
299         return findNodeByFilter(node, resourceId, RESOURCE_ID_FILTER);
300     }
301 
302     /**
303      * Gets a node given the name of its HTML INPUT tag, or {@code null} if not found.
304      */
findNodeByHtmlName(AssistStructure structure, String htmlName)305     static ViewNode findNodeByHtmlName(AssistStructure structure, String htmlName) {
306         return findNodeByFilter(structure, htmlName, HTML_NAME_FILTER);
307     }
308 
309     /**
310      * Gets a node given the name of its HTML INPUT tag, or {@code null} if not found.
311      */
findNodeByHtmlName(List<FillContext> contexts, String htmlName)312     static ViewNode findNodeByHtmlName(List<FillContext> contexts, String htmlName) {
313         return findNodeByFilter(contexts, htmlName, HTML_NAME_FILTER);
314     }
315 
316     /**
317      * Gets a node given the name of its HTML INPUT tag, or {@code null} if not found.
318      */
findNodeByHtmlName(ViewNode node, String htmlName)319     static ViewNode findNodeByHtmlName(ViewNode node, String htmlName) {
320         return findNodeByFilter(node, htmlName, HTML_NAME_FILTER);
321     }
322 
323     /**
324      * Gets a node given the value of its (single) autofill hint property, or {@code null} if not
325      * found.
326      */
findNodeByAutofillHint(ViewNode node, String hint)327     static ViewNode findNodeByAutofillHint(ViewNode node, String hint) {
328         return findNodeByFilter(node, hint, AUTOFILL_HINT_FILTER);
329     }
330 
331     /**
332      * Gets a node given the name of its HTML INPUT tag or Android resoirce id, or {@code null} if
333      * not found.
334      */
findNodeByHtmlNameOrResourceId(List<FillContext> contexts, String id)335     static ViewNode findNodeByHtmlNameOrResourceId(List<FillContext> contexts, String id) {
336         return findNodeByFilter(contexts, id, HTML_NAME_OR_RESOURCE_ID_FILTER);
337     }
338 
339     /**
340      * Gets the {@code name} attribute of a node representing an HTML input tag.
341      */
342     @Nullable
getHtmlName(@onNull ViewNode node)343     static String getHtmlName(@NonNull ViewNode node) {
344         final HtmlInfo htmlInfo = node.getHtmlInfo();
345         if (htmlInfo == null) {
346             return null;
347         }
348         final String tag = htmlInfo.getTag();
349         if (!"input".equals(tag)) {
350             Log.w(TAG, "getHtmlName(): invalid tag (" + tag + ") on " + htmlInfo);
351             return null;
352         }
353         for (Pair<String, String> attr : htmlInfo.getAttributes()) {
354             if ("name".equals(attr.first)) {
355                 return attr.second;
356             }
357         }
358         Log.w(TAG, "getHtmlName(): no 'name' attribute on " + htmlInfo);
359         return null;
360     }
361 
362     /**
363      * Gets a node given its expected text, or {@code null} if not found.
364      */
findNodeByText(AssistStructure structure, String text)365     static ViewNode findNodeByText(AssistStructure structure, String text) {
366         return findNodeByFilter(structure, text, TEXT_FILTER);
367     }
368 
369     /**
370      * Gets a node given its expected text, or {@code null} if not found.
371      */
findNodeByText(ViewNode node, String text)372     static ViewNode findNodeByText(ViewNode node, String text) {
373         return findNodeByFilter(node, text, TEXT_FILTER);
374     }
375 
376     /**
377      * Gets a view that contains the an autofill hint, or {@code null} if not found.
378      */
findViewByAutofillHint(Activity activity, String hint)379     static View findViewByAutofillHint(Activity activity, String hint) {
380         final View rootView = activity.getWindow().getDecorView().getRootView();
381         return findViewByAutofillHint(rootView, hint);
382     }
383 
384     /**
385      * Gets a view (or a descendant of it) that contains the an autofill hint, or {@code null} if
386      * not found.
387      */
findViewByAutofillHint(View view, String hint)388     static View findViewByAutofillHint(View view, String hint) {
389         if (AUTOFILL_HINT_VIEW_FILTER.matches(view, hint)) return view;
390         if ((view instanceof ViewGroup)) {
391             final ViewGroup group = (ViewGroup) view;
392             for (int i = 0; i < group.getChildCount(); i++) {
393                 final View child = findViewByAutofillHint(group.getChildAt(i), hint);
394                 if (child != null) return child;
395             }
396         }
397         return null;
398     }
399 
400     /**
401      * Gets a view (or a descendant of it) that has the given {@code id}, or {@code null} if
402      * not found.
403      */
findNodeByAutofillId(AssistStructure structure, AutofillId id)404     static ViewNode findNodeByAutofillId(AssistStructure structure, AutofillId id) {
405         return findNodeByFilter(structure, id, AUTOFILL_ID_FILTER);
406     }
407 
408     /**
409      * Asserts a text-based node is sanitized.
410      */
assertTextIsSanitized(ViewNode node)411     static void assertTextIsSanitized(ViewNode node) {
412         final CharSequence text = node.getText();
413         final String resourceId = node.getIdEntry();
414         if (!TextUtils.isEmpty(text)) {
415             throw new AssertionError("text on sanitized field " + resourceId + ": " + text);
416         }
417 
418         assertNotFromResources(node);
419         assertNodeHasNoAutofillValue(node);
420     }
421 
assertNotFromResources(ViewNode node)422     private static void assertNotFromResources(ViewNode node) {
423         assertThat(node.getTextIdEntry()).isNull();
424     }
425 
assertNodeHasNoAutofillValue(ViewNode node)426     static void assertNodeHasNoAutofillValue(ViewNode node) {
427         final AutofillValue value = node.getAutofillValue();
428         if (value != null) {
429             final String text = value.isText() ? value.getTextValue().toString() : "N/A";
430             throw new AssertionError("node has value: " + value + " text=" + text);
431         }
432     }
433 
434     /**
435      * Asserts the contents of a text-based node that is also auto-fillable.
436      */
assertTextOnly(ViewNode node, String expectedValue)437     static void assertTextOnly(ViewNode node, String expectedValue) {
438         assertText(node, expectedValue, false);
439         assertNotFromResources(node);
440     }
441 
442     /**
443      * Asserts the contents of a text-based node that is also auto-fillable.
444      */
assertTextOnly(AssistStructure structure, String resourceId, String expectedValue)445     static void assertTextOnly(AssistStructure structure, String resourceId, String expectedValue) {
446         final ViewNode node = findNodeByResourceId(structure, resourceId);
447         assertText(node, expectedValue, false);
448         assertNotFromResources(node);
449     }
450 
451     /**
452      * Asserts the contents of a text-based node that is also auto-fillable.
453      */
assertTextAndValue(ViewNode node, String expectedValue)454     static void assertTextAndValue(ViewNode node, String expectedValue) {
455         assertText(node, expectedValue, true);
456         assertNotFromResources(node);
457     }
458 
459     /**
460      * Asserts a text-based node exists and verify its values.
461      */
assertTextAndValue(AssistStructure structure, String resourceId, String expectedValue)462     static ViewNode assertTextAndValue(AssistStructure structure, String resourceId,
463             String expectedValue) {
464         final ViewNode node = findNodeByResourceId(structure, resourceId);
465         assertTextAndValue(node, expectedValue);
466         return node;
467     }
468 
469     /**
470      * Asserts a text-based node exists and is sanitized.
471      */
assertValue(AssistStructure structure, String resourceId, String expectedValue)472     static ViewNode assertValue(AssistStructure structure, String resourceId,
473             String expectedValue) {
474         final ViewNode node = findNodeByResourceId(structure, resourceId);
475         assertTextValue(node, expectedValue);
476         return node;
477     }
478 
479     /**
480      * Asserts the values of a text-based node whose string come from resoruces.
481      */
assertTextFromResouces(AssistStructure structure, String resourceId, String expectedValue, boolean isAutofillable, String expectedTextIdEntry)482     static ViewNode assertTextFromResouces(AssistStructure structure, String resourceId,
483             String expectedValue, boolean isAutofillable, String expectedTextIdEntry) {
484         final ViewNode node = findNodeByResourceId(structure, resourceId);
485         assertText(node, expectedValue, isAutofillable);
486         assertThat(node.getTextIdEntry()).isEqualTo(expectedTextIdEntry);
487         return node;
488     }
489 
assertText(ViewNode node, String expectedValue, boolean isAutofillable)490     private static void assertText(ViewNode node, String expectedValue, boolean isAutofillable) {
491         assertWithMessage("wrong text on %s", node.getAutofillId()).that(node.getText().toString())
492                 .isEqualTo(expectedValue);
493         final AutofillValue value = node.getAutofillValue();
494         final AutofillId id = node.getAutofillId();
495         if (isAutofillable) {
496             assertWithMessage("null auto-fill value on %s", id).that(value).isNotNull();
497             assertWithMessage("wrong auto-fill value on %s", id)
498                     .that(value.getTextValue().toString()).isEqualTo(expectedValue);
499         } else {
500             assertWithMessage("node %s should not have AutofillValue", id).that(value).isNull();
501         }
502     }
503 
504     /**
505      * Asserts the auto-fill value of a text-based node.
506      */
assertTextValue(ViewNode node, String expectedText)507     static ViewNode assertTextValue(ViewNode node, String expectedText) {
508         final AutofillValue value = node.getAutofillValue();
509         final AutofillId id = node.getAutofillId();
510         assertWithMessage("null autofill value on %s", id).that(value).isNotNull();
511         assertWithMessage("wrong autofill type on %s", id).that(value.isText()).isTrue();
512         assertWithMessage("wrong autofill value on %s", id).that(value.getTextValue().toString())
513                 .isEqualTo(expectedText);
514         return node;
515     }
516 
517     /**
518      * Asserts the auto-fill value of a list-based node.
519      */
assertListValue(ViewNode node, int expectedIndex)520     static ViewNode assertListValue(ViewNode node, int expectedIndex) {
521         final AutofillValue value = node.getAutofillValue();
522         final AutofillId id = node.getAutofillId();
523         assertWithMessage("null autofill value on %s", id).that(value).isNotNull();
524         assertWithMessage("wrong autofill type on %s", id).that(value.isList()).isTrue();
525         assertWithMessage("wrong autofill value on %s", id).that(value.getListValue())
526                 .isEqualTo(expectedIndex);
527         return node;
528     }
529 
530     /**
531      * Asserts the auto-fill value of a toggle-based node.
532      */
assertToggleValue(ViewNode node, boolean expectedToggle)533     static void assertToggleValue(ViewNode node, boolean expectedToggle) {
534         final AutofillValue value = node.getAutofillValue();
535         final AutofillId id = node.getAutofillId();
536         assertWithMessage("null autofill value on %s", id).that(value).isNotNull();
537         assertWithMessage("wrong autofill type on %s", id).that(value.isToggle()).isTrue();
538         assertWithMessage("wrong autofill value on %s", id).that(value.getToggleValue())
539                 .isEqualTo(expectedToggle);
540     }
541 
542     /**
543      * Asserts the auto-fill value of a date-based node.
544      */
assertDateValue(Object object, AutofillValue value, int year, int month, int day)545     static void assertDateValue(Object object, AutofillValue value, int year, int month, int day) {
546         assertWithMessage("null autofill value on %s", object).that(value).isNotNull();
547         assertWithMessage("wrong autofill type on %s", object).that(value.isDate()).isTrue();
548 
549         final Calendar cal = Calendar.getInstance();
550         cal.setTimeInMillis(value.getDateValue());
551 
552         assertWithMessage("Wrong year on AutofillValue %s", value)
553             .that(cal.get(Calendar.YEAR)).isEqualTo(year);
554         assertWithMessage("Wrong month on AutofillValue %s", value)
555             .that(cal.get(Calendar.MONTH)).isEqualTo(month);
556         assertWithMessage("Wrong day on AutofillValue %s", value)
557              .that(cal.get(Calendar.DAY_OF_MONTH)).isEqualTo(day);
558     }
559 
560     /**
561      * Asserts the auto-fill value of a date-based node.
562      */
assertDateValue(ViewNode node, int year, int month, int day)563     static void assertDateValue(ViewNode node, int year, int month, int day) {
564         assertDateValue(node, node.getAutofillValue(), year, month, day);
565     }
566 
567     /**
568      * Asserts the auto-fill value of a date-based view.
569      */
assertDateValue(View view, int year, int month, int day)570     static void assertDateValue(View view, int year, int month, int day) {
571         assertDateValue(view, view.getAutofillValue(), year, month, day);
572     }
573 
574     /**
575      * Asserts the auto-fill value of a time-based node.
576      */
assertTimeValue(Object object, AutofillValue value, int hour, int minute)577     private static void assertTimeValue(Object object, AutofillValue value, int hour, int minute) {
578         assertWithMessage("null autofill value on %s", object).that(value).isNotNull();
579         assertWithMessage("wrong autofill type on %s", object).that(value.isDate()).isTrue();
580 
581         final Calendar cal = Calendar.getInstance();
582         cal.setTimeInMillis(value.getDateValue());
583 
584         assertWithMessage("Wrong hour on AutofillValue %s", value)
585             .that(cal.get(Calendar.HOUR_OF_DAY)).isEqualTo(hour);
586         assertWithMessage("Wrong minute on AutofillValue %s", value)
587             .that(cal.get(Calendar.MINUTE)).isEqualTo(minute);
588     }
589 
590     /**
591      * Asserts the auto-fill value of a time-based node.
592      */
assertTimeValue(ViewNode node, int hour, int minute)593     static void assertTimeValue(ViewNode node, int hour, int minute) {
594         assertTimeValue(node, node.getAutofillValue(), hour, minute);
595     }
596 
597     /**
598      * Asserts the auto-fill value of a time-based view.
599      */
assertTimeValue(View view, int hour, int minute)600     static void assertTimeValue(View view, int hour, int minute) {
601         assertTimeValue(view, view.getAutofillValue(), hour, minute);
602     }
603 
604     /**
605      * Asserts a text-based node exists and is sanitized.
606      */
assertTextIsSanitized(AssistStructure structure, String resourceId)607     static ViewNode assertTextIsSanitized(AssistStructure structure, String resourceId) {
608         final ViewNode node = findNodeByResourceId(structure, resourceId);
609         assertWithMessage("no ViewNode with id %s", resourceId).that(node).isNotNull();
610         assertTextIsSanitized(node);
611         return node;
612     }
613 
614     /**
615      * Asserts a list-based node exists and is sanitized.
616      */
assertListValueIsSanitized(AssistStructure structure, String resourceId)617     static void assertListValueIsSanitized(AssistStructure structure, String resourceId) {
618         final ViewNode node = findNodeByResourceId(structure, resourceId);
619         assertWithMessage("no ViewNode with id %s", resourceId).that(node).isNotNull();
620         assertTextIsSanitized(node);
621     }
622 
623     /**
624      * Asserts a toggle node exists and is sanitized.
625      */
assertToggleIsSanitized(AssistStructure structure, String resourceId)626     static void assertToggleIsSanitized(AssistStructure structure, String resourceId) {
627         final ViewNode node = findNodeByResourceId(structure, resourceId);
628         assertNodeHasNoAutofillValue(node);
629         assertWithMessage("ViewNode %s should not be checked", resourceId).that(node.isChecked())
630                 .isFalse();
631     }
632 
633     /**
634      * Asserts a node exists and has the {@code expected} number of children.
635      */
assertNumberOfChildren(AssistStructure structure, String resourceId, int expected)636     static void assertNumberOfChildren(AssistStructure structure, String resourceId, int expected) {
637         final ViewNode node = findNodeByResourceId(structure, resourceId);
638         final int actual = node.getChildCount();
639         if (actual != expected) {
640             dumpStructure("assertNumberOfChildren()", structure);
641             throw new AssertionError("assertNumberOfChildren() for " + resourceId
642                     + " failed: expected " + expected + ", got " + actual);
643         }
644     }
645 
646     /**
647      * Asserts the number of children in the Assist structure.
648      */
assertNumberOfChildren(AssistStructure structure, int expected)649     static void assertNumberOfChildren(AssistStructure structure, int expected) {
650         assertWithMessage("wrong number of nodes").that(structure.getWindowNodeCount())
651                 .isEqualTo(1);
652         final int actual = getNumberNodes(structure);
653         if (actual != expected) {
654             dumpStructure("assertNumberOfChildren()", structure);
655             throw new AssertionError("assertNumberOfChildren() for structure failed: expected "
656                     + expected + ", got " + actual);
657         }
658     }
659 
660     /**
661      * Gets the total number of nodes in an structure.
662      */
getNumberNodes(AssistStructure structure)663     static int getNumberNodes(AssistStructure structure) {
664         int count = 0;
665         final int nodes = structure.getWindowNodeCount();
666         for (int i = 0; i < nodes; i++) {
667             final WindowNode windowNode = structure.getWindowNodeAt(i);
668             final ViewNode rootNode = windowNode.getRootViewNode();
669             count += getNumberNodes(rootNode);
670         }
671         return count;
672     }
673 
674     /**
675      * Gets the total number of nodes in an node, including all descendants and the node itself.
676      */
getNumberNodes(ViewNode node)677     static int getNumberNodes(ViewNode node) {
678         int count = 1;
679         final int childrenSize = node.getChildCount();
680         if (childrenSize > 0) {
681             for (int i = 0; i < childrenSize; i++) {
682                 count += getNumberNodes(node.getChildAt(i));
683             }
684         }
685         return count;
686     }
687 
688     /**
689      * Creates an array of {@link AutofillId} mapped from the {@code structure} nodes with the given
690      * {@code resourceIds}.
691      */
getAutofillIds(Function<String, ViewNode> nodeResolver, String[] resourceIds)692     static AutofillId[] getAutofillIds(Function<String, ViewNode> nodeResolver,
693             String[] resourceIds) {
694         if (resourceIds == null) return null;
695 
696         final AutofillId[] requiredIds = new AutofillId[resourceIds.length];
697         for (int i = 0; i < resourceIds.length; i++) {
698             final String resourceId = resourceIds[i];
699             final ViewNode node = nodeResolver.apply(resourceId);
700             if (node == null) {
701                 throw new AssertionError("No node with savable resourceId " + resourceId);
702             }
703             requiredIds[i] = node.getAutofillId();
704 
705         }
706         return requiredIds;
707     }
708 
709     /**
710      * Prevents the screen to rotate by itself
711      */
disableAutoRotation(UiBot uiBot)712     public static void disableAutoRotation(UiBot uiBot) throws Exception {
713         runShellCommand(ACCELLEROMETER_CHANGE, 0);
714         uiBot.setScreenOrientation(PORTRAIT);
715     }
716 
717     /**
718      * Allows the screen to rotate by itself
719      */
allowAutoRotation()720     public static void allowAutoRotation() {
721         runShellCommand(ACCELLEROMETER_CHANGE, 1);
722     }
723 
724     /**
725      * Gets the maximum number of partitions per session.
726      */
getMaxPartitions()727     public static int getMaxPartitions() {
728         return Integer.parseInt(runShellCommand("cmd autofill get max_partitions"));
729     }
730 
731     /**
732      * Sets the maximum number of partitions per session.
733      */
setMaxPartitions(int value)734     public static void setMaxPartitions(int value) {
735         runShellCommand("cmd autofill set max_partitions %d", value);
736         assertThat(getMaxPartitions()).isEqualTo(value);
737     }
738 
739     /**
740      * Checks if device supports the Autofill feature.
741      */
hasAutofillFeature()742     public static boolean hasAutofillFeature() {
743         return RequiredFeatureRule.hasFeature(PackageManager.FEATURE_AUTOFILL);
744     }
745 
746     /**
747      * Checks if autofill window is fullscreen, see com.android.server.autofill.ui.FillUi.
748      */
isAutofillWindowFullScreen(Context context)749     public static boolean isAutofillWindowFullScreen(Context context) {
750         return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK);
751     }
752 
753     /**
754      * Checks if screen orientation can be changed.
755      */
isRotationSupported(Context context)756     public static boolean isRotationSupported(Context context) {
757         return !context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK);
758     }
759 
760     /**
761      * Uses Shell command to get the Autofill logging level.
762      */
getLoggingLevel()763     public static String getLoggingLevel() {
764         return runShellCommand("cmd autofill get log_level");
765     }
766 
767     /**
768      * Uses Shell command to set the Autofill logging level.
769      */
setLoggingLevel(String level)770     public static void setLoggingLevel(String level) {
771         runShellCommand("cmd autofill set log_level %s", level);
772     }
773 
774     /**
775      * Uses Settings to enable the given autofill service for the default user, and checks the
776      * value was properly check, throwing an exception if it was not.
777      */
enableAutofillService(@onNull Context context, @NonNull String serviceName)778     public static void enableAutofillService(@NonNull Context context,
779             @NonNull String serviceName) {
780         if (isAutofillServiceEnabled(serviceName)) return;
781 
782         SettingsHelper.syncSet(context, AUTOFILL_SERVICE, serviceName);
783     }
784 
785     /**
786      * Uses Settings to disable the given autofill service for the default user, and checks the
787      * value was properly check, throwing an exception if it was not.
788      */
disableAutofillService(@onNull Context context, @NonNull String serviceName)789     public static void disableAutofillService(@NonNull Context context,
790             @NonNull String serviceName) {
791         if (!isAutofillServiceEnabled(serviceName)) return;
792 
793         SettingsHelper.syncDelete(context, AUTOFILL_SERVICE);
794     }
795 
796     /**
797      * Checks whether the given service is set as the autofill service for the default user.
798      */
isAutofillServiceEnabled(@onNull String serviceName)799     private static boolean isAutofillServiceEnabled(@NonNull String serviceName) {
800         final String actualName = SettingsHelper.get(AUTOFILL_SERVICE);
801         return serviceName.equals(actualName);
802     }
803 
804     /**
805      * Asserts whether the given service is enabled as the autofill service for the default user.
806      */
assertAutofillServiceStatus(@onNull String serviceName, boolean enabled)807     public static void assertAutofillServiceStatus(@NonNull String serviceName, boolean enabled) {
808         final String actual = SettingsHelper.get(AUTOFILL_SERVICE);
809         final String expected = enabled ? serviceName : "null";
810         assertWithMessage("Invalid value for secure setting %s", AUTOFILL_SERVICE)
811                 .that(actual).isEqualTo(expected);
812     }
813 
814     /**
815      * Asserts that there is a pending session for the given package.
816      */
assertHasSessions(String packageName)817     public static void assertHasSessions(String packageName) {
818         final String result = runShellCommand(CMD_LIST_SESSIONS);
819         assertThat(result).contains(packageName);
820     }
821 
822     /**
823      * Gets the instrumentation context.
824      */
getContext()825     public static Context getContext() {
826         return InstrumentationRegistry.getInstrumentation().getContext();
827     }
828 
829     /**
830      * Cleans up the autofill state; should be called before pretty much any test.
831      */
preTestCleanup()832     public static void preTestCleanup() {
833         if (!hasAutofillFeature()) return;
834 
835         Log.d(TAG, "preTestCleanup()");
836 
837         disableAutofillService(getContext(), SERVICE_NAME);
838         InstrumentedAutoFillService.setIgnoreUnexpectedRequests(true);
839 
840         InstrumentedAutoFillService.resetStaticState();
841         AuthenticationActivity.resetStaticState();
842     }
843 
844     /**
845      * Asserts the node has an {@code HTMLInfo} property, with the given tag.
846      */
assertHasHtmlTag(ViewNode node, String expectedTag)847     public static HtmlInfo assertHasHtmlTag(ViewNode node, String expectedTag) {
848         final HtmlInfo info = node.getHtmlInfo();
849         assertWithMessage("node doesn't have htmlInfo").that(info).isNotNull();
850         assertWithMessage("wrong tag").that(info.getTag()).isEqualTo(expectedTag);
851         return info;
852     }
853 
854     /**
855      * Gets the value of an {@code HTMLInfo} attribute.
856      */
857     @Nullable
getAttributeValue(HtmlInfo info, String attribute)858     public static String getAttributeValue(HtmlInfo info, String attribute) {
859         for (Pair<String, String> pair : info.getAttributes()) {
860             if (pair.first.equals(attribute)) {
861                 return pair.second;
862             }
863         }
864         return null;
865     }
866 
867     /**
868      * Asserts a {@code HTMLInfo} has an attribute with a given value.
869      */
assertHasAttribute(HtmlInfo info, String attribute, String expectedValue)870     public static void assertHasAttribute(HtmlInfo info, String attribute, String expectedValue) {
871         final String actualValue = getAttributeValue(info, attribute);
872         assertWithMessage("Attribute %s not found", attribute).that(actualValue).isNotNull();
873         assertWithMessage("Wrong value for Attribute %s", attribute)
874             .that(actualValue).isEqualTo(expectedValue);
875     }
876 
877     /**
878      * Finds a {@link WebView} node given its expected form name.
879      */
findWebViewNodeByFormName(AssistStructure structure, String formName)880     public static ViewNode findWebViewNodeByFormName(AssistStructure structure, String formName) {
881         return findNodeByFilter(structure, formName, WEBVIEW_FORM_FILTER);
882     }
883 
assertClientState(Object container, Bundle clientState, String key, String value)884     private static void assertClientState(Object container, Bundle clientState,
885             String key, String value) {
886         assertWithMessage("'%s' should have client state", container)
887             .that(clientState).isNotNull();
888         assertWithMessage("Wrong number of client state extras on '%s'", container)
889             .that(clientState.keySet().size()).isEqualTo(1);
890         assertWithMessage("Wrong value for client state key (%s) on '%s'", key, container)
891             .that(clientState.getString(key)).isEqualTo(value);
892     }
893 
894     /**
895      * Asserts the content of a {@link FillEventHistory#getClientState()}.
896      *
897      * @param history event to be asserted
898      * @param key the only key expected in the client state bundle
899      * @param value the only value expected in the client state bundle
900      */
901     @SuppressWarnings("javadoc")
assertDeprecatedClientState(@onNull FillEventHistory history, @NonNull String key, @NonNull String value)902     public static void assertDeprecatedClientState(@NonNull FillEventHistory history,
903             @NonNull String key, @NonNull String value) {
904         assertThat(history).isNotNull();
905         @SuppressWarnings("deprecation")
906         final Bundle clientState = history.getClientState();
907         assertClientState(history, clientState, key, value);
908     }
909 
910     /**
911      * Asserts the {@link FillEventHistory#getClientState()} is not set.
912      *
913      * @param history event to be asserted
914      */
915     @SuppressWarnings("javadoc")
assertNoDeprecatedClientState(@onNull FillEventHistory history)916     public static void assertNoDeprecatedClientState(@NonNull FillEventHistory history) {
917         assertThat(history).isNotNull();
918         @SuppressWarnings("deprecation")
919         final Bundle clientState = history.getClientState();
920         assertWithMessage("History '%s' should not have client state", history)
921              .that(clientState).isNull();
922     }
923 
924     /**
925      * Asserts the content of a {@link android.service.autofill.FillEventHistory.Event}.
926      *
927      * @param event event to be asserted
928      * @param eventType expected type
929      * @param datasetId dataset set id expected in the event
930      * @param key the only key expected in the client state bundle (or {@code null} if it shouldn't
931      * have client state)
932      * @param value the only value expected in the client state bundle (or {@code null} if it
933      * shouldn't have client state)
934      * @param fieldClassificationResults expected results when asserting field classification
935      */
assertFillEvent(@onNull FillEventHistory.Event event, int eventType, @Nullable String datasetId, @Nullable String key, @Nullable String value, @Nullable FieldClassificationResult[] fieldClassificationResults)936     private static void assertFillEvent(@NonNull FillEventHistory.Event event,
937             int eventType, @Nullable String datasetId,
938             @Nullable String key, @Nullable String value,
939             @Nullable FieldClassificationResult[] fieldClassificationResults) {
940         assertThat(event).isNotNull();
941         assertWithMessage("Wrong type for %s", event).that(event.getType()).isEqualTo(eventType);
942         if (datasetId == null) {
943             assertWithMessage("Event %s should not have dataset id", event)
944                 .that(event.getDatasetId()).isNull();
945         } else {
946             assertWithMessage("Wrong dataset id for %s", event)
947                 .that(event.getDatasetId()).isEqualTo(datasetId);
948         }
949         final Bundle clientState = event.getClientState();
950         if (key == null) {
951             assertWithMessage("Event '%s' should not have client state", event)
952                 .that(clientState).isNull();
953         } else {
954             assertClientState(event, clientState, key, value);
955         }
956         assertWithMessage("Event '%s' should not have selected datasets", event)
957                 .that(event.getSelectedDatasetIds()).isEmpty();
958         assertWithMessage("Event '%s' should not have ignored datasets", event)
959                 .that(event.getIgnoredDatasetIds()).isEmpty();
960         assertWithMessage("Event '%s' should not have changed fields", event)
961                 .that(event.getChangedFields()).isEmpty();
962         assertWithMessage("Event '%s' should not have manually-entered fields", event)
963                 .that(event.getManuallyEnteredField()).isEmpty();
964         final Map<AutofillId, FieldClassification> detectedFields = event.getFieldsClassification();
965         if (fieldClassificationResults == null) {
966             assertThat(detectedFields).isEmpty();
967         } else {
968             assertThat(detectedFields).hasSize(fieldClassificationResults.length);
969             int i = 0;
970             for (Entry<AutofillId, FieldClassification> entry : detectedFields.entrySet()) {
971                 assertMatches(i, entry, fieldClassificationResults[i]);
972                 i++;
973             }
974         }
975     }
976 
assertMatches(int i, Entry<AutofillId, FieldClassification> actualResult, FieldClassificationResult expectedResult)977     private static void assertMatches(int i, Entry<AutofillId, FieldClassification> actualResult,
978             FieldClassificationResult expectedResult) {
979         assertWithMessage("Wrong field id at index %s", i).that(actualResult.getKey())
980                 .isEqualTo(expectedResult.id);
981         final List<Match> matches = actualResult.getValue().getMatches();
982         assertWithMessage("Wrong number of matches: " + matches).that(matches.size())
983                 .isEqualTo(expectedResult.remoteIds.length);
984         for (int j = 0; j < matches.size(); j++) {
985             final Match match = matches.get(j);
986             assertWithMessage("Wrong categoryId at (%s, %s): %s", i, j, match)
987                 .that(match.getCategoryId()).isEqualTo(expectedResult.remoteIds[j]);
988             assertWithMessage("Wrong score at (%s, %s): %s", i, j, match)
989                 .that(match.getScore()).isWithin(0.01f).of(expectedResult.scores[j]);
990         }
991     }
992 
993     /**
994      * Asserts the content of a
995      * {@link android.service.autofill.FillEventHistory.Event#TYPE_DATASET_SELECTED} event.
996      *
997      * @param event event to be asserted
998      * @param datasetId dataset set id expected in the event
999      */
assertFillEventForDatasetSelected(@onNull FillEventHistory.Event event, @Nullable String datasetId)1000     public static void assertFillEventForDatasetSelected(@NonNull FillEventHistory.Event event,
1001             @Nullable String datasetId) {
1002         assertFillEvent(event, TYPE_DATASET_SELECTED, datasetId, null, null, null);
1003     }
1004 
1005     /**
1006      * Asserts the content of a
1007      * {@link android.service.autofill.FillEventHistory.Event#TYPE_DATASET_SELECTED} event.
1008      *
1009      * @param event event to be asserted
1010      * @param datasetId dataset set id expected in the event
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      */
assertFillEventForDatasetSelected(@onNull FillEventHistory.Event event, @Nullable String datasetId, @Nullable String key, @Nullable String value)1014     public static void assertFillEventForDatasetSelected(@NonNull FillEventHistory.Event event,
1015             @Nullable String datasetId, @Nullable String key, @Nullable String value) {
1016         assertFillEvent(event, TYPE_DATASET_SELECTED, datasetId, key, value, null);
1017     }
1018 
1019     /**
1020      * Asserts the content of a
1021      * {@link android.service.autofill.FillEventHistory.Event#TYPE_SAVE_SHOWN} event.
1022      *
1023      * @param event event to be asserted
1024      * @param datasetId dataset set id expected in the event
1025      * @param key the only key expected in the client state bundle
1026      * @param value the only value expected in the client state bundle
1027      */
assertFillEventForSaveShown(@onNull FillEventHistory.Event event, @NonNull String datasetId, @NonNull String key, @NonNull String value)1028     public static void assertFillEventForSaveShown(@NonNull FillEventHistory.Event event,
1029             @NonNull String datasetId, @NonNull String key, @NonNull String value) {
1030         assertFillEvent(event, TYPE_SAVE_SHOWN, datasetId, key, value, null);
1031     }
1032 
1033     /**
1034      * Asserts the content of a
1035      * {@link android.service.autofill.FillEventHistory.Event#TYPE_SAVE_SHOWN} event.
1036      *
1037      * @param event event to be asserted
1038      * @param datasetId dataset set id expected in the event
1039      */
assertFillEventForSaveShown(@onNull FillEventHistory.Event event, @NonNull String datasetId)1040     public static void assertFillEventForSaveShown(@NonNull FillEventHistory.Event event,
1041             @NonNull String datasetId) {
1042         assertFillEvent(event, TYPE_SAVE_SHOWN, datasetId, null, null, null);
1043     }
1044 
1045     /**
1046      * Asserts the content of a
1047      * {@link android.service.autofill.FillEventHistory.Event#TYPE_DATASET_AUTHENTICATION_SELECTED}
1048      * event.
1049      *
1050      * @param event event to be asserted
1051      * @param datasetId dataset set id expected in the event
1052      * @param key the only key expected in the client state bundle
1053      * @param value the only value expected in the client state bundle
1054      */
assertFillEventForDatasetAuthenticationSelected( @onNull FillEventHistory.Event event, @Nullable String datasetId, @NonNull String key, @NonNull String value)1055     public static void assertFillEventForDatasetAuthenticationSelected(
1056             @NonNull FillEventHistory.Event event,
1057             @Nullable String datasetId, @NonNull String key, @NonNull String value) {
1058         assertFillEvent(event, TYPE_DATASET_AUTHENTICATION_SELECTED, datasetId, key, value, null);
1059     }
1060 
1061     /**
1062      * Asserts the content of a
1063      * {@link android.service.autofill.FillEventHistory.Event#TYPE_AUTHENTICATION_SELECTED} event.
1064      *
1065      * @param event event to be asserted
1066      * @param datasetId dataset set id expected in the event
1067      * @param key the only key expected in the client state bundle
1068      * @param value the only value expected in the client state bundle
1069      */
assertFillEventForAuthenticationSelected( @onNull FillEventHistory.Event event, @Nullable String datasetId, @NonNull String key, @NonNull String value)1070     public static void assertFillEventForAuthenticationSelected(
1071             @NonNull FillEventHistory.Event event,
1072             @Nullable String datasetId, @NonNull String key, @NonNull String value) {
1073         assertFillEvent(event, TYPE_AUTHENTICATION_SELECTED, datasetId, key, value, null);
1074     }
1075 
assertFillEventForFieldsClassification(@onNull FillEventHistory.Event event, @NonNull AutofillId fieldId, @NonNull String remoteId, float score)1076     public static void assertFillEventForFieldsClassification(@NonNull FillEventHistory.Event event,
1077             @NonNull AutofillId fieldId, @NonNull String remoteId, float score) {
1078         assertFillEvent(event, TYPE_CONTEXT_COMMITTED, null, null, null,
1079                 new FieldClassificationResult[] {
1080                         new FieldClassificationResult(fieldId, remoteId, score)
1081                 });
1082     }
1083 
assertFillEventForFieldsClassification(@onNull FillEventHistory.Event event, @NonNull FieldClassificationResult[] results)1084     public static void assertFillEventForFieldsClassification(@NonNull FillEventHistory.Event event,
1085             @NonNull FieldClassificationResult[] results) {
1086         assertFillEvent(event, TYPE_CONTEXT_COMMITTED, null, null, null, results);
1087     }
1088 
assertFillEventForContextCommitted(@onNull FillEventHistory.Event event)1089     public static void assertFillEventForContextCommitted(@NonNull FillEventHistory.Event event) {
1090         assertFillEvent(event, TYPE_CONTEXT_COMMITTED, null, null, null, null);
1091     }
1092 
1093     @NonNull
getActivityName(List<FillContext> contexts)1094     public static String getActivityName(List<FillContext> contexts) {
1095         if (contexts == null) return "N/A (null contexts)";
1096 
1097         if (contexts.isEmpty()) return "N/A (empty contexts)";
1098 
1099         final AssistStructure structure = contexts.get(contexts.size() - 1).getStructure();
1100         if (structure == null) return "N/A (no AssistStructure)";
1101 
1102         final ComponentName componentName = structure.getActivityComponent();
1103         if (componentName == null) return "N/A (no component name)";
1104 
1105         return componentName.flattenToShortString();
1106     }
1107 
assertFloat(float actualValue, float expectedValue)1108     public static void assertFloat(float actualValue, float expectedValue) {
1109         assertThat(actualValue).isWithin(1.0e-10f).of(expectedValue);
1110     }
1111 
assertHasFlags(int actualFlags, int expectedFlags)1112     public static void assertHasFlags(int actualFlags, int expectedFlags) {
1113         assertWithMessage("Flags %s not in %s", expectedFlags, actualFlags)
1114                 .that(actualFlags & expectedFlags).isEqualTo(expectedFlags);
1115     }
1116 
callbackEventAsString(int event)1117     public static String callbackEventAsString(int event) {
1118         switch (event) {
1119             case AutofillCallback.EVENT_INPUT_HIDDEN:
1120                 return "HIDDEN";
1121             case AutofillCallback.EVENT_INPUT_SHOWN:
1122                 return "SHOWN";
1123             case AutofillCallback.EVENT_INPUT_UNAVAILABLE:
1124                 return "UNAVAILABLE";
1125             default:
1126                 return "UNKNOWN:" + event;
1127         }
1128     }
1129 
importantForAutofillAsString(int mode)1130     public static String importantForAutofillAsString(int mode) {
1131         switch (mode) {
1132             case View.IMPORTANT_FOR_AUTOFILL_AUTO:
1133                 return "IMPORTANT_FOR_AUTOFILL_AUTO";
1134             case View.IMPORTANT_FOR_AUTOFILL_YES:
1135                 return "IMPORTANT_FOR_AUTOFILL_YES";
1136             case View.IMPORTANT_FOR_AUTOFILL_YES_EXCLUDE_DESCENDANTS:
1137                 return "IMPORTANT_FOR_AUTOFILL_YES_EXCLUDE_DESCENDANTS";
1138             case View.IMPORTANT_FOR_AUTOFILL_NO:
1139                 return "IMPORTANT_FOR_AUTOFILL_NO";
1140             case View.IMPORTANT_FOR_AUTOFILL_NO_EXCLUDE_DESCENDANTS:
1141                 return "IMPORTANT_FOR_AUTOFILL_NO_EXCLUDE_DESCENDANTS";
1142             default:
1143                 return "UNKNOWN:" + mode;
1144         }
1145     }
1146 
hasHint(@ullable String[] hints, @Nullable Object expectedHint)1147     public static boolean hasHint(@Nullable String[] hints, @Nullable Object expectedHint) {
1148         if (hints == null || expectedHint == null) return false;
1149         for (String actualHint : hints) {
1150             if (expectedHint.equals(actualHint)) return true;
1151         }
1152         return false;
1153     }
1154 
1155     /**
1156      * Asserts that 2 bitmaps have are the same. If they aren't throws an exception and dump them
1157      * locally so their can be visually inspected.
1158      *
1159      * @param filename base name of the files generated in case of error
1160      * @param bitmap1 first bitmap to be compared
1161      * @param bitmap2 second bitmap to be compared
1162      */
assertBitmapsAreSame(@onNull String filename, @Nullable Bitmap bitmap1, @Nullable Bitmap bitmap2)1163     public static void assertBitmapsAreSame(@NonNull String filename, @Nullable Bitmap bitmap1,
1164             @Nullable Bitmap bitmap2) throws IOException {
1165         assertWithMessage("1st bitmap is null").that(bitmap1).isNotNull();
1166         assertWithMessage("2nd bitmap is null").that(bitmap2).isNotNull();
1167         final boolean same = bitmap1.sameAs(bitmap2);
1168         if (same) {
1169             Log.v(TAG, "bitmap comparison passed for " + filename);
1170             return;
1171         }
1172 
1173         final File dir = new File(LOCAL_DIRECTORY);
1174         dir.mkdirs();
1175         if (!dir.exists()) {
1176             Log.e(TAG, "Could not create directory " + dir);
1177             throw new AssertionError("bitmap comparison failed for " + filename
1178                     + ", and bitmaps could not be dumped on " + dir);
1179         }
1180         final File dump1 = dumpBitmap(bitmap1, dir, filename + "-1.png");
1181         final File dump2 = dumpBitmap(bitmap2, dir, filename + "-2.png");
1182         throw new AssertionError(
1183                 "bitmap comparison failed; check contents of " + dump1 + " and " + dump2);
1184     }
1185 
1186     @Nullable
dumpBitmap(@onNull Bitmap bitmap, @NonNull File dir, @NonNull String filename)1187     private static File dumpBitmap(@NonNull Bitmap bitmap, @NonNull File dir,
1188             @NonNull String filename) throws IOException {
1189         final File file = new File(dir, filename);
1190         if (file.exists()) {
1191             file.delete();
1192         }
1193         if (!file.createNewFile()) {
1194             Log.e(TAG, "Could not create file " + file);
1195             return null;
1196         }
1197         Log.d(TAG, "Dumping bitmap at " + file);
1198         BitmapUtils.saveBitmap(bitmap, file.getParent(), file.getName());
1199         return file;
1200     }
1201 
Helper()1202     private Helper() {
1203         throw new UnsupportedOperationException("contain static methods only");
1204     }
1205 
1206     static class FieldClassificationResult {
1207         public final AutofillId id;
1208         public final String[] remoteIds;
1209         public final float[] scores;
1210 
FieldClassificationResult(@onNull AutofillId id, @NonNull String remoteId, float score)1211         FieldClassificationResult(@NonNull AutofillId id, @NonNull String remoteId, float score) {
1212             this(id, new String[] { remoteId }, new float[] { score });
1213         }
1214 
FieldClassificationResult(@onNull AutofillId id, @NonNull String[] remoteIds, float[] scores)1215         FieldClassificationResult(@NonNull AutofillId id, @NonNull String[] remoteIds,
1216                 float[] scores) {
1217             this.id = id;
1218             this.remoteIds = remoteIds;
1219             this.scores = scores;
1220         }
1221     }
1222 }
1223