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