• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 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.platform.helpers;
18 
19 import static android.platform.helpers.ui.UiAutomatorUtils.getInstrumentation;
20 import static android.platform.helpers.ui.UiAutomatorUtils.getUiDevice;
21 import static android.platform.helpers.ui.UiSearch.search;
22 import static android.platform.uiautomator_helpers.DeviceHelpers.getContext;
23 
24 import static com.google.common.truth.Truth.assertThat;
25 import static com.google.common.truth.Truth.assertWithMessage;
26 
27 import static java.lang.String.format;
28 
29 import android.app.Instrumentation;
30 import android.content.ComponentName;
31 import android.content.Intent;
32 import android.content.pm.PackageManager;
33 import android.content.res.Configuration;
34 import android.graphics.Point;
35 import android.graphics.Rect;
36 import android.os.Bundle;
37 import android.os.ParcelFileDescriptor;
38 import android.os.RemoteException;
39 import android.platform.helpers.features.common.HomeLockscreenPage;
40 import android.platform.helpers.ui.UiSearch2;
41 import android.platform.test.util.HealthTestingUtils;
42 import android.support.test.uiautomator.BySelector;
43 import android.support.test.uiautomator.UiObject;
44 import android.support.test.uiautomator.UiObjectNotFoundException;
45 import android.util.Log;
46 
47 import androidx.test.platform.app.InstrumentationRegistry;
48 
49 import java.io.FileInputStream;
50 import java.io.IOException;
51 import java.util.ArrayList;
52 import java.util.Arrays;
53 import java.util.List;
54 import java.util.regex.Matcher;
55 import java.util.regex.Pattern;
56 
57 /**
58  * Helper class for writing System UI ui tests. It consists of common utils required while writing
59  * UI tests.
60  */
61 public class CommonUtils {
62 
63     private static final int LARGE_SCREEN_DP_THRESHOLD = 600;
64     private static final String TAG = "CommonUtils";
65     private static final int SWIPE_STEPS = 100;
66     private static final int DEFAULT_MARGIN = 5;
67     private static final String LIST_ALL_USERS_COMMAND = "cmd user list -v --all";
68     private static final String GET_MAIN_USER_COMMAND = "cmd user get-main-user";
69 
CommonUtils()70     private CommonUtils() {
71     }
72 
73     /**
74      * Prints a message to standard output during an instrumentation test.
75      *
76      * Message will be printed to terminal if test is run using {@code am instrument}. This is
77      * useful for debugging.
78      */
println(String msg)79     public static void println(String msg) {
80         final Bundle streamResult = new Bundle();
81         streamResult.putString(Instrumentation.REPORT_KEY_STREAMRESULT, msg + "\n");
82         InstrumentationRegistry.getInstrumentation().sendStatus(0, streamResult);
83     }
84 
85     /**
86      * This method help you execute you shell command.
87      * Example: adb shell pm list packages -f
88      * Here you just need to provide executeShellCommand("pm list packages -f")
89      *
90      * @param command command need to executed.
91      * @return output in String format.
92      */
executeShellCommand(String command)93     public static String executeShellCommand(String command) {
94         Log.d(TAG, format("Executing Shell Command: %s", command));
95         try {
96             String out = getUiDevice().executeShellCommand(command);
97             return out;
98         } catch (IOException e) {
99             Log.d(TAG, format("IOException Occurred: %s", e));
100             throw new RuntimeException(e);
101         }
102     }
103 
104     /** Returns PIDs of all System UI processes */
getSystemUiPids()105     private static String[] getSystemUiPids() {
106         String output = executeShellCommand("pidof com.android.systemui");
107         if (output.isEmpty()) {
108             // explicit check empty string, and return 0-length array.
109             // "".split("\\s") returns 1-length array [""], which invalidates
110             // allSysUiProcessesRestarted check.
111             return new String[0];
112         }
113         return output.split("\\s");
114     }
115 
allSysUiProcessesRestarted(List<String> initialPidsList)116     private static boolean allSysUiProcessesRestarted(List<String> initialPidsList) {
117         final String[] currentPids = getSystemUiPids();
118         Log.d(TAG, "restartSystemUI: Current PIDs=" + Arrays.toString(currentPids));
119         if (currentPids.length < initialPidsList.size()) {
120             return false; // No all processes restarted.
121         }
122         for (String pid : currentPids) {
123             if (initialPidsList.contains(pid)) {
124                 return false; // Old process still running.
125             }
126         }
127         return true;
128     }
129 
130     /**
131      * Restart System UI by running {@code am crash com.android.systemui}.
132      *
133      * <p>This is sometimes necessary after changing flags, configs, or settings ensure that
134      * systemui is properly initialized with the new changes. This method will wait until the home
135      * screen is visible, then it will optionally dismiss the home screen via swipe.
136      *
137      * @param swipeUp whether to call {@link HomeLockscreenPage#swipeUp()} after restarting System
138      *     UI
139      * @deprecated Use {@link SysuiRestarter} instead. It has been moved out from here to use
140      *     androidx uiautomator version (this class depends on the old version, and there are many
141      *     deps that don't allow to easily switch to the new androidx one)
142      */
143     @Deprecated
restartSystemUI(boolean swipeUp)144     public static void restartSystemUI(boolean swipeUp) {
145         SysuiRestarter.restartSystemUI(swipeUp);
146     }
147 
148     /** Asserts that the screen is on. */
assertScreenOn(String errorMessage)149     public static void assertScreenOn(String errorMessage) {
150         try {
151             assertWithMessage(errorMessage)
152                     .that(getUiDevice().isScreenOn())
153                     .isTrue();
154         } catch (RemoteException e) {
155             throw new RuntimeException(e);
156         }
157     }
158 
159     /**
160      * Helper method to swipe the given object.
161      *
162      * @param gestureType direction which to swipe.
163      * @param obj         object which needs to be swiped.
164      */
swipe(GestureType gestureType, UiObject obj)165     public static void swipe(GestureType gestureType, UiObject obj) {
166         Log.d(TAG, format("Swiping Object[%s] %s", obj.getSelector(), gestureType));
167         try {
168             Rect boundary = obj.getBounds();
169             final int displayHeight = getUiDevice().getDisplayHeight() - DEFAULT_MARGIN;
170             final int displayWidth = getUiDevice().getDisplayWidth() - DEFAULT_MARGIN;
171             final int objHeight = boundary.height();
172             final int objWidth = boundary.width();
173             final int marginHeight = (Math.abs(displayHeight - objHeight)) / 2;
174             final int marginWidth = (Math.abs(displayWidth - objWidth)) / 2;
175             switch (gestureType) {
176                 case DOWN:
177                     getUiDevice().swipe(
178                             marginWidth + (objWidth / 2),
179                             marginHeight,
180                             marginWidth + (objWidth / 2),
181                             displayHeight,
182                             SWIPE_STEPS
183                     );
184                     break;
185                 case UP:
186                     getUiDevice().swipe(
187                             marginWidth + (objWidth / 2),
188                             displayHeight,
189                             marginWidth + (objWidth / 2),
190                             marginHeight,
191                             SWIPE_STEPS
192                     );
193                     break;
194                 case RIGHT:
195                     getUiDevice().swipe(
196                             marginWidth,
197                             marginHeight + (objHeight / 2),
198                             displayWidth,
199                             marginHeight + (objHeight / 2),
200                             SWIPE_STEPS
201                     );
202                     break;
203                 case LEFT:
204                     getUiDevice().swipe(
205                             displayWidth,
206                             marginHeight + (objHeight / 2),
207                             marginWidth,
208                             marginHeight + (objHeight / 2),
209                             SWIPE_STEPS
210                     );
211                     break;
212             }
213         } catch (UiObjectNotFoundException e) {
214             Log.e(TAG,
215                     format("Given object was not found. Hence failed to swipe. Exception %s", e));
216             throw new RuntimeException(e);
217         }
218     }
219 
220     /**
221      * Launching an app with different ways.
222      *
223      * @param launchAppWith                  options used to launching an app.
224      * @param packageActivityOrComponentName required package or activity or component name to
225      *                                       launch the given app
226      * @param appName                        name of the app
227      */
launchApp(LaunchAppWith launchAppWith, String packageActivityOrComponentName, String appName)228     public static void launchApp(LaunchAppWith launchAppWith, String packageActivityOrComponentName,
229             String appName) {
230         Log.d(TAG, String.format("Opening app %s using their %s [%s]",
231                 appName, launchAppWith, packageActivityOrComponentName));
232         Intent appIntent = null;
233         switch (launchAppWith) {
234             case PACKAGE_NAME:
235                 PackageManager packageManager = getContext().getPackageManager();
236                 appIntent = packageManager.getLaunchIntentForPackage(
237                         packageActivityOrComponentName);
238                 appIntent.addCategory(Intent.CATEGORY_LAUNCHER);
239                 break;
240             case ACTIVITY:
241                 appIntent = new Intent(packageActivityOrComponentName);
242                 break;
243             case COMPONENT_NAME:
244                 ComponentName componentName = ComponentName.unflattenFromString(
245                         packageActivityOrComponentName);
246                 appIntent = new Intent();
247                 appIntent.setComponent(componentName);
248                 break;
249             default:
250                 throw new AssertionError("Non-supported Launch App with: " + launchAppWith);
251         }
252         // Ensure the app is completely restarted so that none of the test app's state
253         // leaks between tests.
254         appIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
255         getContext().startActivity(appIntent);
256     }
257 
258     /**
259      * Asserts that a given page is visible.
260      *
261      * @param pageSelector      selector helped to verify the page
262      * @param pageName          name of the page to be verified
263      * @param maxTimeoutSeconds max time in seconds to verify the page
264      */
assertPageVisible(BySelector pageSelector, String pageName, int maxTimeoutSeconds)265     public static void assertPageVisible(BySelector pageSelector, String pageName,
266             int maxTimeoutSeconds) {
267         assertWithMessage(format("Page[%s] not visible; selector: %s", pageName, pageSelector))
268                 .that(search(null, pageSelector, format("Page[%s]", pageName), maxTimeoutSeconds))
269                 .isTrue();
270     }
271 
272     /**
273      * Asserts that a given page is visible.
274      *
275      * @param pageSelector      selector helped to verify the page
276      * @param pageName          name of the page to be verified
277      * @param maxTimeoutSeconds max time in seconds to verify the page
278      */
assertPageVisible(androidx.test.uiautomator.BySelector pageSelector, String pageName, int maxTimeoutSeconds)279     public static void assertPageVisible(androidx.test.uiautomator.BySelector pageSelector,
280             String pageName, int maxTimeoutSeconds) {
281         assertThat(UiSearch2.search(null, pageSelector, format("Page[%s]", pageName),
282                 maxTimeoutSeconds)).isTrue();
283     }
284 
285     /**
286      * Asserts that a given page is not visible.
287      *
288      * @param pageSelector selector helped to verify the page
289      * @param pageName     name of the page to be verified
290      */
assertPageNotVisible(BySelector pageSelector, String pageName)291     public static void assertPageNotVisible(BySelector pageSelector, String pageName) {
292         HealthTestingUtils.waitForCondition(
293                 () -> "Page is still visible",
294                 () -> !search(null, pageSelector, format("Page[%s]", pageName), 0));
295     }
296 
297     /**
298      * Asserts that a given page is visible.
299      *
300      * @param pageSelector      selector helped to verify the page
301      * @param pageName          name of the page to be verified
302      * @param maxTimeoutSeconds max time in seconds to verify the page
303      */
assertPageNotVisible(androidx.test.uiautomator.BySelector pageSelector, String pageName, int maxTimeoutSeconds)304     public static void assertPageNotVisible(androidx.test.uiautomator.BySelector pageSelector,
305             String pageName, int maxTimeoutSeconds) {
306         assertThat(UiSearch2.search(null, pageSelector, format("Page[%s]", pageName),
307                 maxTimeoutSeconds)).isFalse();
308     }
309 
310     /**
311      * Execute the given shell command and get the detailed output
312      *
313      * @param shellCommand shell command to be executed
314      * @return the detailed output as an arraylist.
315      */
executeShellCommandWithDetailedOutput(String shellCommand)316     public static ArrayList<String> executeShellCommandWithDetailedOutput(String shellCommand) {
317         try {
318             ParcelFileDescriptor fileDescriptor =
319                     getInstrumentation().getUiAutomation().executeShellCommand(shellCommand);
320             byte[] buf = new byte[512];
321             int bytesRead;
322             FileInputStream inputStream = new ParcelFileDescriptor.AutoCloseInputStream(
323                     fileDescriptor);
324             ArrayList<String> output = new ArrayList<>();
325             while ((bytesRead = inputStream.read(buf)) != -1) {
326                 output.add(new String(buf, 0, bytesRead));
327             }
328             inputStream.close();
329             return output;
330         } catch (IOException e) {
331             throw new RuntimeException(e);
332         }
333     }
334 
335     /**
336      * Returns the current user user ID. NOTE: UserID = 0 is for System
337      *
338      * @return a current user ID
339      */
getCurrentUserId()340     public static int getCurrentUserId() {
341         Log.d(TAG, "Getting the Current User ID");
342 
343         // Example terminal output of the list all users command:
344         //
345         //  $ adb shell cmd user list -v --all
346         // 2 users:
347         //
348         // 0: id=0, name=Owner, type=full.SYSTEM, flags=FULL|INITIALIZED|PRIMARY|SYSTEM (running)
349         // 1: id=10, name=Guest, type=full.GUEST, flags=FULL|GUEST|INITIALIZED (running) (current)
350         ArrayList<String> output = executeShellCommandWithDetailedOutput(LIST_ALL_USERS_COMMAND);
351         String getCurrentUser = null;
352         for (String line : output) {
353             if (line.contains("(current)")) {
354                 getCurrentUser = line;
355                 break;
356             }
357         }
358         Pattern userRegex = Pattern.compile("[\\d]+:.*id=([\\d]+).*\\(current\\)");
359         Matcher matcher = userRegex.matcher(getCurrentUser);
360         while (matcher.find()) {
361             return Integer.parseInt(matcher.group(1));
362         }
363 
364         Log.d(TAG, "Failed to find current user ID. dumpsys activity follows:");
365         for (String line : output) {
366             Log.d(TAG, line);
367         }
368         throw new RuntimeException("Failed to find current user ID.");
369     }
370 
371     /**
372      * Returns the main user ID. Main user is the main human user on the device.
373      * Returns 0 by default, if there is no main user. Android Auto is example of HSUM without
374      * main user.
375      *
376      * NOTE: For headless system main user it is NOT 0. Therefore Main user should be used in
377      * test cases rather than owner or deprecated primary user.
378      */
getMainUserId()379     public static int getMainUserId() {
380         ArrayList<String> output = executeShellCommandWithDetailedOutput(GET_MAIN_USER_COMMAND);
381         try {
382             return Integer.parseInt(output.get(0).trim());
383         } catch (NumberFormatException e) {
384             return 0;
385         }
386     }
387 
isSplitShade()388     public static boolean isSplitShade() {
389         int orientation = getContext().getResources().getConfiguration().orientation;
390         return isLargeScreen() && orientation == Configuration.ORIENTATION_LANDSCAPE;
391     }
392 
isLargeScreen()393     public static boolean isLargeScreen() {
394         Point sizeDp = getUiDevice().getDisplaySizeDp();
395         return sizeDp.x >= LARGE_SCREEN_DP_THRESHOLD && sizeDp.y >= LARGE_SCREEN_DP_THRESHOLD;
396     }
397 
398     /**
399      * Gesture for swipe
400      */
401     public enum GestureType {
402         RIGHT,
403         LEFT,
404         UP,
405         DOWN
406     }
407 
408     /**
409      * Different options used for launching an app.
410      */
411     public enum LaunchAppWith {
412         PACKAGE_NAME,
413         ACTIVITY,
414         COMPONENT_NAME
415     }
416 }
417