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.Helper.runShellCommand;
20 import static android.provider.Settings.Secure.AUTOFILL_SERVICE;
21 
22 import static com.google.common.truth.Truth.assertThat;
23 import static com.google.common.truth.Truth.assertWithMessage;
24 
25 import android.app.assist.AssistStructure;
26 import android.app.assist.AssistStructure.ViewNode;
27 import android.app.assist.AssistStructure.WindowNode;
28 import android.content.Context;
29 import android.content.pm.PackageManager;
30 import android.icu.util.Calendar;
31 import android.os.UserManager;
32 import android.service.autofill.FillContext;
33 import android.support.annotation.NonNull;
34 import android.support.test.InstrumentationRegistry;
35 import android.text.TextUtils;
36 import android.util.Log;
37 import android.view.View;
38 import android.view.ViewStructure.HtmlInfo;
39 import android.view.autofill.AutofillId;
40 import android.view.autofill.AutofillValue;
41 
42 import com.android.compatibility.common.util.SystemUtil;
43 
44 import java.util.List;
45 import java.util.function.Function;
46 
47 /**
48  * Helper for common funcionalities.
49  */
50 final class Helper {
51 
52     private static final String TAG = "AutoFillCtsHelper";
53 
54     // TODO: should static import Settings.Secure instead, but that's not a @TestApi
55     private static String USER_SETUP_COMPLETE = "user_setup_complete";
56 
57     static final boolean VERBOSE = false;
58 
59     static final String ID_USERNAME_LABEL = "username_label";
60     static final String ID_USERNAME = "username";
61     static final String ID_PASSWORD_LABEL = "password_label";
62     static final String ID_PASSWORD = "password";
63     static final String ID_LOGIN = "login";
64     static final String ID_OUTPUT = "output";
65 
66     /** Pass to {@link #setOrientation(int)} to change the display to portrait mode */
67     public static int PORTRAIT = 0;
68 
69     /** Pass to {@link #setOrientation(int)} to change the display to landscape mode */
70     public static int LANDSCAPE = 1;
71 
72     /**
73      * Timeout (in milliseconds) until framework binds / unbinds from service.
74      */
75     static final long CONNECTION_TIMEOUT_MS = 2000;
76 
77     /**
78      * Timeout (in milliseconds) until framework unbinds from a service.
79      */
80     static final long IDLE_UNBIND_TIMEOUT_MS = 5000;
81 
82     /**
83      * Timeout (in milliseconds) for expected auto-fill requests.
84      */
85     static final long FILL_TIMEOUT_MS = 2000;
86 
87     /**
88      * Timeout (in milliseconds) for expected save requests.
89      */
90     static final long SAVE_TIMEOUT_MS = 5000;
91 
92     /**
93      * Time to wait if a UI change is not expected
94      */
95     static final long NOT_SHOWING_TIMEOUT_MS = 500;
96 
97     /**
98      * Timeout (in milliseconds) for UI operations. Typically used by {@link UiBot}.
99      */
100     static final int UI_TIMEOUT_MS = 2000;
101 
102     /**
103      * Time to wait in between retries
104      */
105     static final int RETRY_MS = 100;
106 
107     private final static String ACCELLEROMETER_CHANGE =
108             "content insert --uri content://settings/system --bind name:s:accelerometer_rotation "
109                     + "--bind value:i:%d";
110     private final static String ORIENTATION_CHANGE =
111             "content insert --uri content://settings/system --bind name:s:user_rotation --bind "
112                     + "value:i:%d";
113 
114     /**
115      * Runs a {@code r}, ignoring all {@link RuntimeException} and {@link Error} until the
116      * {@link #UI_TIMEOUT_MS} is reached.
117      */
eventually(Runnable r)118     static void eventually(Runnable r) throws Exception {
119         eventually(r, UI_TIMEOUT_MS);
120     }
121 
122     /**
123      * Runs a {@code r}, ignoring all {@link RuntimeException} and {@link Error} until the
124      * {@code timeout} is reached.
125      */
eventually(Runnable r, int timeout)126     static void eventually(Runnable r, int timeout) throws Exception {
127         long startTime = System.currentTimeMillis();
128 
129         while (true) {
130             try {
131                 r.run();
132                 break;
133             } catch (RuntimeException | Error e) {
134                 if (System.currentTimeMillis() - startTime < timeout) {
135                     if (VERBOSE) Log.v(TAG, "Ignoring", e);
136                     Thread.sleep(RETRY_MS);
137                 } else {
138                     if (e instanceof RetryableException) {
139                         throw e;
140                     } else {
141                         throw new Exception("Timedout out after " + timeout + " ms", e);
142                     }
143                 }
144             }
145         }
146     }
147 
148     /**
149      * Runs a Shell command, returning a trimmed response.
150      */
runShellCommand(String template, Object...args)151     static String runShellCommand(String template, Object...args) {
152         final String command = String.format(template, args);
153         Log.d(TAG, "runShellCommand(): " + command);
154         try {
155             final String result = SystemUtil
156                     .runShellCommand(InstrumentationRegistry.getInstrumentation(), command);
157             return TextUtils.isEmpty(result) ? "" : result.trim();
158         } catch (Exception e) {
159             throw new RuntimeException("Command '" + command + "' failed: ", e);
160         }
161     }
162 
163     /**
164      * Dump the assist structure on logcat.
165      */
dumpStructure(String message, AssistStructure structure)166     static void dumpStructure(String message, AssistStructure structure) {
167         final StringBuffer buffer = new StringBuffer(message)
168                 .append(": component=")
169                 .append(structure.getActivityComponent());
170         final int nodes = structure.getWindowNodeCount();
171         for (int i = 0; i < nodes; i++) {
172             final WindowNode windowNode = structure.getWindowNodeAt(i);
173             dump(buffer, windowNode.getRootViewNode(), " ", 0);
174         }
175         Log.i(TAG, buffer.toString());
176     }
177 
178     /**
179      * Dump the contexts on logcat.
180      */
dumpStructure(String message, List<FillContext> contexts)181     static void dumpStructure(String message, List<FillContext> contexts) {
182         for (FillContext context : contexts) {
183             dumpStructure(message, context.getStructure());
184         }
185     }
186 
187     /**
188      * Dumps the state of the autofill service on logcat.
189      */
dumpAutofillService()190     static void dumpAutofillService() {
191         Log.i(TAG, "dumpsys autofill\n\n" + runShellCommand("dumpsys autofill"));
192     }
193 
194     /**
195      * Sets whether the user completed the initial setup.
196      */
setUserComplete(Context context, boolean complete)197     static void setUserComplete(Context context, boolean complete) {
198         if (isUserComplete() == complete) return;
199 
200         final OneTimeSettingsListener observer = new OneTimeSettingsListener(context,
201                 USER_SETUP_COMPLETE);
202         final String newValue = complete ? "1" : null;
203         runShellCommand("settings put secure %s %s default", USER_SETUP_COMPLETE, newValue);
204         observer.assertCalled();
205 
206         assertIsUserComplete(complete);
207     }
208 
209     /**
210      * Gets whether the user completed the initial setup.
211      */
isUserComplete()212     static boolean isUserComplete() {
213         final String isIt = runShellCommand("settings get secure %s", USER_SETUP_COMPLETE);
214         return "1".equals(isIt);
215     }
216 
217     /**
218      * Assets that user completed (or not) the initial setup.
219      */
assertIsUserComplete(boolean expected)220     static void assertIsUserComplete(boolean expected) {
221         final boolean actual = isUserComplete();
222         assertWithMessage("Invalid value for secure setting %s", USER_SETUP_COMPLETE)
223                 .that(actual).isEqualTo(expected);
224     }
225 
dump(StringBuffer buffer, ViewNode node, String prefix, int childId)226     private static void dump(StringBuffer buffer, ViewNode node, String prefix, int childId) {
227         final int childrenSize = node.getChildCount();
228         buffer.append("\n").append(prefix)
229             .append('#').append(childId).append(':')
230             .append("resId=").append(node.getIdEntry())
231             .append(" class=").append(node.getClassName())
232             .append(" text=").append(node.getText())
233             .append(" class=").append(node.getClassName())
234             .append(" #children=").append(childrenSize);
235 
236         buffer.append("\n").append(prefix)
237             .append("   afId=").append(node.getAutofillId())
238             .append(" afType=").append(node.getAutofillType())
239             .append(" afValue=").append(node.getAutofillValue())
240             .append(" checked=").append(node.isChecked())
241             .append(" focused=").append(node.isFocused());
242 
243         final HtmlInfo htmlInfo = node.getHtmlInfo();
244         if (htmlInfo != null) {
245             buffer.append("\nHtmlInfo: tag=").append(htmlInfo.getTag())
246                 .append(", attrs: ").append(htmlInfo.getAttributes());
247         }
248 
249         prefix += " ";
250         if (childrenSize > 0) {
251             for (int i = 0; i < childrenSize; i++) {
252                 dump(buffer, node.getChildAt(i), prefix, i);
253             }
254         }
255     }
256 
257     /**
258      * Gets a node given its Android resource id, or {@code null} if not found.
259      */
findNodeByResourceId(AssistStructure structure, String resourceId)260     static ViewNode findNodeByResourceId(AssistStructure structure, String resourceId) {
261         Log.v(TAG, "Parsing request for activity " + structure.getActivityComponent());
262         final int nodes = structure.getWindowNodeCount();
263         for (int i = 0; i < nodes; i++) {
264             final WindowNode windowNode = structure.getWindowNodeAt(i);
265             final ViewNode rootNode = windowNode.getRootViewNode();
266             final ViewNode node = findNodeByResourceId(rootNode, resourceId);
267             if (node != null) {
268                 return node;
269             }
270         }
271         return null;
272     }
273 
274     /**
275      * Gets a node given its Android resource id, or {@code null} if not found.
276      */
findNodeByResourceId(List<FillContext> contexts, String resourceId)277     static ViewNode findNodeByResourceId(List<FillContext> contexts, String resourceId) {
278         for (FillContext context : contexts) {
279             ViewNode node = findNodeByResourceId(context.getStructure(), resourceId);
280             if (node != null) {
281                 return node;
282             }
283         }
284         return null;
285     }
286 
287     /**
288      * Gets a node given its Android resource id, or {@code null} if not found.
289      */
findNodeByResourceId(ViewNode node, String resourceId)290     static ViewNode findNodeByResourceId(ViewNode node, String resourceId) {
291         if (resourceId.equals(node.getIdEntry())) {
292             return node;
293         }
294         final int childrenSize = node.getChildCount();
295         if (childrenSize > 0) {
296             for (int i = 0; i < childrenSize; i++) {
297                 final ViewNode found = findNodeByResourceId(node.getChildAt(i), resourceId);
298                 if (found != null) {
299                     return found;
300                 }
301             }
302         }
303         return null;
304     }
305 
306     /**
307      * Gets a node given its expected text, or {@code null} if not found.
308      */
findNodeByText(AssistStructure structure, String text)309     static ViewNode findNodeByText(AssistStructure structure, String text) {
310         Log.v(TAG, "Parsing request for activity " + structure.getActivityComponent());
311         final int nodes = structure.getWindowNodeCount();
312         for (int i = 0; i < nodes; i++) {
313             final WindowNode windowNode = structure.getWindowNodeAt(i);
314             final ViewNode rootNode = windowNode.getRootViewNode();
315             final ViewNode node = findNodeByText(rootNode, text);
316             if (node != null) {
317                 return node;
318             }
319         }
320         return null;
321     }
322 
323     /**
324      * Gets a node given its expected text, or {@code null} if not found.
325      */
findNodeByText(ViewNode node, String text)326     static ViewNode findNodeByText(ViewNode node, String text) {
327         if (text.equals(node.getText())) {
328             return node;
329         }
330         final int childrenSize = node.getChildCount();
331         if (childrenSize > 0) {
332             for (int i = 0; i < childrenSize; i++) {
333                 final ViewNode found = findNodeByText(node.getChildAt(i), text);
334                 if (found != null) {
335                     return found;
336                 }
337             }
338         }
339         return null;
340     }
341 
342     /**
343      * Asserts a text-base node is sanitized.
344      */
assertTextIsSanitized(ViewNode node)345     static void assertTextIsSanitized(ViewNode node) {
346       final CharSequence text = node.getText();
347       final String resourceId = node.getIdEntry();
348       if (!TextUtils.isEmpty(text)) {
349           throw new AssertionError("text on sanitized field " + resourceId + ": " + text);
350       }
351       assertNodeHasNoAutofillValue(node);
352     }
353 
assertNodeHasNoAutofillValue(ViewNode node)354     static void assertNodeHasNoAutofillValue(ViewNode node) {
355         final AutofillValue value = node.getAutofillValue();
356         if (value != null) {
357             final String text = value.isText() ? value.getTextValue().toString() : "N/A";
358             throw new AssertionError("node has value: " + value + " text=" + text);
359         }
360     }
361 
362     /**
363      * Asserts the contents of a text-based node that is also auto-fillable.
364      *
365      */
assertTextOnly(ViewNode node, String expectedValue)366     static void assertTextOnly(ViewNode node, String expectedValue) {
367         assertText(node, expectedValue, false);
368     }
369 
370     /**
371      * Asserts the contents of a text-based node that is also auto-fillable.
372      *
373      */
assertTextAndValue(ViewNode node, String expectedValue)374     static void assertTextAndValue(ViewNode node, String expectedValue) {
375         assertText(node, expectedValue, true);
376     }
377 
378     /**
379      * Asserts a text-base node exists and verify its values.
380      */
assertTextAndValue(AssistStructure structure, String resourceId, String expectedValue)381     static ViewNode assertTextAndValue(AssistStructure structure, String resourceId,
382             String expectedValue) {
383         final ViewNode node = findNodeByResourceId(structure, resourceId);
384         assertTextAndValue(node, expectedValue);
385         return node;
386     }
387 
388     /**
389      * Asserts a text-base node exists and is sanitized.
390      */
assertValue(AssistStructure structure, String resourceId, String expectedValue)391     static ViewNode assertValue(AssistStructure structure, String resourceId,
392             String expectedValue) {
393         final ViewNode node = findNodeByResourceId(structure, resourceId);
394         assertTextValue(node, expectedValue);
395         return node;
396     }
397 
assertText(ViewNode node, String expectedValue, boolean isAutofillable)398     private static void assertText(ViewNode node, String expectedValue, boolean isAutofillable) {
399         assertWithMessage("wrong text on %s", node).that(node.getText().toString())
400                 .isEqualTo(expectedValue);
401         final AutofillValue value = node.getAutofillValue();
402         if (isAutofillable) {
403             assertWithMessage("null auto-fill value on %s", node).that(value).isNotNull();
404             assertWithMessage("wrong auto-fill value on %s", node)
405                     .that(value.getTextValue().toString()).isEqualTo(expectedValue);
406         } else {
407             assertWithMessage("node %s should not have AutofillValue", node).that(value).isNull();
408         }
409     }
410 
411     /**
412      * Asserts the auto-fill value of a text-based node.
413      */
assertTextValue(ViewNode node, String expectedText)414     static ViewNode assertTextValue(ViewNode node, String expectedText) {
415         final AutofillValue value = node.getAutofillValue();
416         assertWithMessage("null autofill value on %s", node).that(value).isNotNull();
417         assertWithMessage("wrong autofill type on %s", node).that(value.isText()).isTrue();
418         assertWithMessage("wrong autofill value on %s", node).that(value.getTextValue().toString())
419                 .isEqualTo(expectedText);
420         return node;
421     }
422 
423     /**
424      * Asserts the auto-fill value of a list-based node.
425      */
assertListValue(ViewNode node, int expectedIndex)426     static ViewNode assertListValue(ViewNode node, int expectedIndex) {
427         final AutofillValue value = node.getAutofillValue();
428         assertWithMessage("null autofill value on %s", node).that(value).isNotNull();
429         assertWithMessage("wrong autofill type on %s", node).that(value.isList()).isTrue();
430         assertWithMessage("wrong autofill value on %s", node).that(value.getListValue())
431                 .isEqualTo(expectedIndex);
432         return node;
433     }
434 
435     /**
436      * Asserts the auto-fill value of a toggle-based node.
437      */
assertToggleValue(ViewNode node, boolean expectedToggle)438     static void assertToggleValue(ViewNode node, boolean expectedToggle) {
439         final AutofillValue value = node.getAutofillValue();
440         assertWithMessage("null autofill value on %s", node).that(value).isNotNull();
441         assertWithMessage("wrong autofill type on %s", node).that(value.isToggle()).isTrue();
442         assertWithMessage("wrong autofill value on %s", node).that(value.getToggleValue())
443                 .isEqualTo(expectedToggle);
444     }
445 
446     /**
447      * Asserts the auto-fill value of a date-based node.
448      */
assertDateValue(Object object, AutofillValue value, int year, int month, int day)449     static void assertDateValue(Object object, AutofillValue value, int year, int month, int day) {
450         assertWithMessage("null autofill value on %s", object).that(value).isNotNull();
451         assertWithMessage("wrong autofill type on %s", object).that(value.isDate()).isTrue();
452 
453         final Calendar cal = Calendar.getInstance();
454         cal.setTimeInMillis(value.getDateValue());
455 
456         assertWithMessage("Wrong year on AutofillValue %s", value)
457             .that(cal.get(Calendar.YEAR)).isEqualTo(year);
458         assertWithMessage("Wrong month on AutofillValue %s", value)
459             .that(cal.get(Calendar.MONTH)).isEqualTo(month);
460         assertWithMessage("Wrong day on AutofillValue %s", value)
461              .that(cal.get(Calendar.DAY_OF_MONTH)).isEqualTo(day);
462     }
463 
464     /**
465      * Asserts the auto-fill value of a date-based node.
466      */
assertDateValue(ViewNode node, int year, int month, int day)467     static void assertDateValue(ViewNode node, int year, int month, int day) {
468         assertDateValue(node, node.getAutofillValue(), year, month, day);
469     }
470 
471     /**
472      * Asserts the auto-fill value of a date-based view.
473      */
assertDateValue(View view, int year, int month, int day)474     static void assertDateValue(View view, int year, int month, int day) {
475         assertDateValue(view, view.getAutofillValue(), year, month, day);
476     }
477 
478     /**
479      * Asserts the auto-fill value of a time-based node.
480      */
assertTimeValue(Object object, AutofillValue value, int hour, int minute)481     private static void assertTimeValue(Object object, AutofillValue value, int hour, int minute) {
482         assertWithMessage("null autofill value on %s", object).that(value).isNotNull();
483         assertWithMessage("wrong autofill type on %s", object).that(value.isDate()).isTrue();
484 
485         final Calendar cal = Calendar.getInstance();
486         cal.setTimeInMillis(value.getDateValue());
487 
488         assertWithMessage("Wrong hour on AutofillValue %s", value)
489             .that(cal.get(Calendar.HOUR_OF_DAY)).isEqualTo(hour);
490         assertWithMessage("Wrong minute on AutofillValue %s", value)
491             .that(cal.get(Calendar.MINUTE)).isEqualTo(minute);
492     }
493 
494     /**
495      * Asserts the auto-fill value of a time-based node.
496      */
assertTimeValue(ViewNode node, int hour, int minute)497     static void assertTimeValue(ViewNode node, int hour, int minute) {
498         assertTimeValue(node, node.getAutofillValue(), hour, minute);
499     }
500 
501     /**
502      * Asserts the auto-fill value of a time-based view.
503      */
assertTimeValue(View view, int hour, int minute)504     static void assertTimeValue(View view, int hour, int minute) {
505         assertTimeValue(view, view.getAutofillValue(), hour, minute);
506     }
507 
508     /**
509      * Asserts a text-base node exists and is sanitized.
510      */
assertTextIsSanitized(AssistStructure structure, String resourceId)511     static ViewNode assertTextIsSanitized(AssistStructure structure, String resourceId) {
512         final ViewNode node = findNodeByResourceId(structure, resourceId);
513         assertWithMessage("no ViewNode with id %s", resourceId).that(node).isNotNull();
514         assertTextIsSanitized(node);
515         return node;
516     }
517 
518     /**
519      * Asserts a list-based node exists and is sanitized.
520      */
assertListValueIsSanitized(AssistStructure structure, String resourceId)521     static void assertListValueIsSanitized(AssistStructure structure, String resourceId) {
522         final ViewNode node = findNodeByResourceId(structure, resourceId);
523         assertWithMessage("no ViewNode with id %s", resourceId).that(node).isNotNull();
524         assertTextIsSanitized(node);
525     }
526 
527     /**
528      * Asserts a toggle node exists and is sanitized.
529      */
assertToggleIsSanitized(AssistStructure structure, String resourceId)530     static void assertToggleIsSanitized(AssistStructure structure, String resourceId) {
531         final ViewNode node = findNodeByResourceId(structure, resourceId);
532         assertNodeHasNoAutofillValue(node);
533         assertWithMessage("ViewNode %s should not be checked", resourceId).that(node.isChecked())
534                 .isFalse();
535     }
536 
537     /**
538      * Asserts a node exists and has the {@code expected} number of children.
539      */
assertNumberOfChildren(AssistStructure structure, String resourceId, int expected)540     static void assertNumberOfChildren(AssistStructure structure, String resourceId, int expected) {
541         final ViewNode node = findNodeByResourceId(structure, resourceId);
542         final int actual = node.getChildCount();
543         if (actual != expected) {
544             dumpStructure("assertNumberOfChildren()", structure);
545             throw new AssertionError("assertNumberOfChildren() for " + resourceId
546                     + " failed: expected " + expected + ", got " + actual);
547         }
548     }
549 
550     /**
551      * Asserts the number of children in the Assist structure.
552      */
assertNumberOfChildren(AssistStructure structure, int expected)553     static void assertNumberOfChildren(AssistStructure structure, int expected) {
554         assertWithMessage("wrong number of nodes").that(structure.getWindowNodeCount())
555                 .isEqualTo(1);
556         final int actual = getNumberNodes(structure);
557         if (actual != expected) {
558             dumpStructure("assertNumberOfChildren()", structure);
559             throw new AssertionError("assertNumberOfChildren() for structure failed: expected "
560                     + expected + ", got " + actual);
561         }
562     }
563 
564     /**
565      * Gets the total number of nodes in an structure, including all descendants.
566      */
getNumberNodes(AssistStructure structure)567     static int getNumberNodes(AssistStructure structure) {
568         int count = 0;
569         final int nodes = structure.getWindowNodeCount();
570         for (int i = 0; i < nodes; i++) {
571             final WindowNode windowNode = structure.getWindowNodeAt(i);
572             final ViewNode rootNode = windowNode.getRootViewNode();
573             count += getNumberNodes(rootNode);
574         }
575         return count;
576     }
577 
578     /**
579      * Gets the total number of nodes in an node, including all descendants and the node itself.
580      */
getNumberNodes(ViewNode node)581     private static int getNumberNodes(ViewNode node) {
582         int count = 1;
583         final int childrenSize = node.getChildCount();
584         if (childrenSize > 0) {
585             for (int i = 0; i < childrenSize; i++) {
586                 count += getNumberNodes(node.getChildAt(i));
587             }
588         }
589         return count;
590     }
591 
592     /**
593      * Creates an array of {@link AutofillId} mapped from the {@code structure} nodes with the given
594      * {@code resourceIds}.
595      */
getAutofillIds(Function<String, ViewNode> nodeResolver, String[] resourceIds)596     static AutofillId[] getAutofillIds(Function<String, ViewNode> nodeResolver,
597             String[] resourceIds) {
598         if (resourceIds == null) return null;
599 
600         final AutofillId[] requiredIds = new AutofillId[resourceIds.length];
601         for (int i = 0; i < resourceIds.length; i++) {
602             final String resourceId = resourceIds[i];
603             final ViewNode node = nodeResolver.apply(resourceId);
604             if (node == null) {
605                 throw new AssertionError("No node with savable resourceId " + resourceId);
606             }
607             requiredIds[i] = node.getAutofillId();
608 
609         }
610         return requiredIds;
611     }
612 
613     /**
614      * Prevents the screen to rotate by itself
615      */
disableAutoRotation()616     public static void disableAutoRotation() {
617         runShellCommand(ACCELLEROMETER_CHANGE, 0);
618         setOrientation(PORTRAIT);
619     }
620 
621     /**
622      * Allows the screen to rotate by itself
623      */
allowAutoRotation()624     public static void allowAutoRotation() {
625         runShellCommand(ACCELLEROMETER_CHANGE, 1);
626     }
627 
628     /**
629      * Changes the screen orientation. This triggers a activity lifecycle (destroy -> create) for
630      * activities that do not handle this config change such as {@link OutOfProcessLoginActivity}.
631      *
632      * @param value {@link #PORTRAIT} or {@link #LANDSCAPE};
633      */
setOrientation(int value)634     public static void setOrientation(int value) {
635         runShellCommand(ORIENTATION_CHANGE, value);
636     }
637 
638     /**
639      * Wait until a process starts and returns the process ID of the process.
640      *
641      * @return The pid of the process
642      */
getOutOfProcessPid(@onNull String processName)643     public static int getOutOfProcessPid(@NonNull String processName) throws InterruptedException {
644         long startTime = System.currentTimeMillis();
645 
646         while (System.currentTimeMillis() - startTime < UI_TIMEOUT_MS) {
647             String[] allProcessDescs = runShellCommand("ps -eo PID,ARGS=CMD").split("\n");
648 
649             for (String processDesc : allProcessDescs) {
650                 String[] pidAndName = processDesc.trim().split(" ");
651 
652                 if (pidAndName[1].equals(processName)) {
653                     return Integer.parseInt(pidAndName[0]);
654                 }
655             }
656 
657             Thread.sleep(RETRY_MS);
658         }
659 
660         throw new IllegalStateException("process not found");
661     }
662 
663     /**
664      * Gets the maximum number of partitions per session.
665      */
getMaxPartitions()666     public static int getMaxPartitions() {
667         return Integer.parseInt(runShellCommand("cmd autofill get max_partitions"));
668     }
669 
670     /**
671      * Sets the maximum number of partitions per session.
672      */
setMaxPartitions(int value)673     public static void setMaxPartitions(int value) {
674         runShellCommand("cmd autofill set max_partitions %d", value);
675         assertThat(getMaxPartitions()).isEqualTo(value);
676     }
677 
678     /**
679      * Checks if device supports the Autofill feature.
680      */
hasAutofillFeature()681     public static boolean hasAutofillFeature() {
682         return RequiredFeatureRule.hasFeature(PackageManager.FEATURE_AUTOFILL);
683     }
684 
685     /**
686      * Uses Shell command to get the Autofill logging level.
687      */
getLoggingLevel()688     public static String getLoggingLevel() {
689         return runShellCommand("cmd autofill get log_level");
690     }
691 
692     /**
693      * Uses Shell command to set the Autofill logging level.
694      */
setLoggingLevel(String level)695     public static void setLoggingLevel(String level) {
696         runShellCommand("cmd autofill set log_level %s", level);
697     }
698 
Helper()699     private Helper() {
700     }
701 }
702