diff --git a/wa/framework/uiauto-androidx/app/build.gradle b/wa/framework/uiauto-androidx/app/build.gradle new file mode 100644 index 00000000..180de6b3 --- /dev/null +++ b/wa/framework/uiauto-androidx/app/build.gradle @@ -0,0 +1,18 @@ +apply plugin: 'com.android.library' + +android { + namespace "com.arm.wa.uiauto" + compileSdkVersion 28 + defaultConfig { + minSdkVersion 18 + targetSdkVersion 28 + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } +} + +dependencies { + implementation fileTree(include: ['*.jar'], dir: 'libs') + implementation 'androidx.test:runner:1.6.1' + implementation 'androidx.test:rules:1.6.1' + implementation 'androidx.test.uiautomator:uiautomator-v18:2.2.0-alpha1' +} diff --git a/wa/framework/uiauto-androidx/app/src/main/AndroidManifest.xml b/wa/framework/uiauto-androidx/app/src/main/AndroidManifest.xml new file mode 100644 index 00000000..9d2cf726 --- /dev/null +++ b/wa/framework/uiauto-androidx/app/src/main/AndroidManifest.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/wa/framework/uiauto-androidx/app/src/main/java/com/arm/wa/uiauto/ActionLogger.java b/wa/framework/uiauto-androidx/app/src/main/java/com/arm/wa/uiauto/ActionLogger.java new file mode 100644 index 00000000..f5376184 --- /dev/null +++ b/wa/framework/uiauto-androidx/app/src/main/java/com/arm/wa/uiauto/ActionLogger.java @@ -0,0 +1,60 @@ +/* Copyright 2014-2018 ARM Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.arm.wa.uiauto; + +import android.os.Bundle; +import android.util.Log; + /** + * Basic marker API for workloads to generate start and end markers for + * deliminating and timing actions. Markers are output to logcat with debug + * priority. Actions represent a series of UI interactions to time. + * + * The marker API provides a way for instruments and output processors to hook into + * per-action timings by parsing logcat logs produced per workload iteration. + * + * The marker output consists of a logcat tag 'UX_PERF' and a message. The + * message consists of a name for the action and a timestamp. The timestamp + * is separated by a single space from the name of the action. + * + * Typical usage: + * + * ActionLogger logger = ActionLogger("testTag", parameters); + * logger.start(); + * // actions to be recorded + * logger.stop(); + */ + public class ActionLogger { + + private String testTag; + private boolean enabled; + + public ActionLogger(String testTag, Bundle parameters) { + this.testTag = testTag; + this.enabled = parameters.getBoolean("markers_enabled"); + } + + public void start() { + if (enabled) { + Log.d("UX_PERF", testTag + " start " + System.nanoTime()); + } + } + + public void stop() throws Exception { + if (enabled) { + Log.d("UX_PERF", testTag + " end " + System.nanoTime()); + } + } + } diff --git a/wa/framework/uiauto-androidx/app/src/main/java/com/arm/wa/uiauto/ApplaunchInterface.java b/wa/framework/uiauto-androidx/app/src/main/java/com/arm/wa/uiauto/ApplaunchInterface.java new file mode 100644 index 00000000..cbfb8b9e --- /dev/null +++ b/wa/framework/uiauto-androidx/app/src/main/java/com/arm/wa/uiauto/ApplaunchInterface.java @@ -0,0 +1,54 @@ +/* Copyright 2013-2017 ARM Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +package com.arm.wa.uiauto; + +import android.os.Bundle; +import androidx.test.uiautomator.UiObject; + +/** + * ApplaunchInterface.java + * Interface used for enabling uxperfapplaunch workload. + * This interface gets implemented by all workloads that support application launch + * instrumentation. + */ + +public interface ApplaunchInterface { + + /** + * Sets the launchEndObject of a workload, which is a UiObject that marks + * the end of the application launch. + */ + public UiObject getLaunchEndObject(); + + /** + * Runs the Uiautomation methods for clearing the initial run + * dialogues on the first time installation of an application package. + */ + public void runApplicationSetup() throws Exception; + + /** + * Provides the application launch command of the application which is + * constructed as a string from the workload. + */ + public String getLaunchCommand(); + + /** Passes the workload parameters. */ + public void setWorkloadParameters(Bundle parameters); + + /** Initialize the instrumentation for the workload */ + public void initialize_instrumentation(); + +} diff --git a/wa/framework/uiauto-androidx/app/src/main/java/com/arm/wa/uiauto/BaseUiAutomation.java b/wa/framework/uiauto-androidx/app/src/main/java/com/arm/wa/uiauto/BaseUiAutomation.java new file mode 100644 index 00000000..6e1f2a0d --- /dev/null +++ b/wa/framework/uiauto-androidx/app/src/main/java/com/arm/wa/uiauto/BaseUiAutomation.java @@ -0,0 +1,725 @@ +/* Copyright 2013-2018 ARM Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +package com.arm.wa.uiauto; + +import android.os.Bundle; +import android.os.SystemClock; +import android.app.Instrumentation; +import android.content.Context; +import android.graphics.Point; +import android.graphics.Rect; + +import androidx.test.InstrumentationRegistry; +import androidx.test.uiautomator.UiDevice; +import androidx.test.uiautomator.UiObject; +import androidx.test.uiautomator.UiObjectNotFoundException; +import androidx.test.uiautomator.UiSelector; +import androidx.test.uiautomator.UiWatcher; +import androidx.test.uiautomator.UiScrollable; + +import org.junit.Before; +import org.junit.Test; + +import java.io.BufferedReader; +import java.io.File; +import java.io.InputStreamReader; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import static androidx.test.InstrumentationRegistry.getArguments; + +public class BaseUiAutomation { + + public enum FindByCriteria { BY_ID, BY_TEXT, BY_DESC }; + public enum Direction { UP, DOWN, LEFT, RIGHT, NULL }; + public enum ScreenOrientation { RIGHT, NATURAL, LEFT, PORTRAIT, LANDSCAPE }; + public enum PinchType { IN, OUT, NULL }; + + // Time in milliseconds + public long uiAutoTimeout = 4 * 1000; + + public static final int CLICK_REPEAT_INTERVAL_MINIMUM = 5; + public static final int CLICK_REPEAT_INTERVAL_DEFAULT = 50; + + public Instrumentation mInstrumentation; + public Context mContext; + public UiDevice mDevice; + + @Before + public void initialize_instrumentation() { + mInstrumentation = InstrumentationRegistry.getInstrumentation(); + mDevice = UiDevice.getInstance(mInstrumentation); + mContext = mInstrumentation.getTargetContext(); + } + + @Test + public void setup() throws Exception { + } + + @Test + public void runWorkload() throws Exception { + } + + @Test + public void extractResults() throws Exception { + } + + @Test + public void teardown() throws Exception { + } + + public void sleep(int second) { + SystemClock.sleep(second * 1000); + } + + // Generate a package ID + public String getPackageID(Bundle parameters) { + String packageName = parameters.getString("package_name"); + return packageName + ":id/"; + } + + public boolean takeScreenshot(String name) { + Bundle params = getArguments(); + String png_dir = params.getString("workdir"); + + try { + return mDevice.takeScreenshot(new File(png_dir, name + ".png")); + } catch (NoSuchMethodError e) { + return true; + } + } + + public void waitText(String text) throws UiObjectNotFoundException { + waitText(text, 600); + } + + public void waitText(String text, int second) throws UiObjectNotFoundException { + UiSelector selector = new UiSelector(); + UiObject text_obj = mDevice.findObject(selector.text(text) + .className("android.widget.TextView")); + waitObject(text_obj, second); + } + + public void waitObject(UiObject obj) throws UiObjectNotFoundException { + waitObject(obj, 600); + } + + public void waitObject(UiObject obj, int second) throws UiObjectNotFoundException { + if (!obj.waitForExists(second * 1000)) { + throw new UiObjectNotFoundException("UiObject is not found: " + + obj.getSelector().toString()); + } + } + + public boolean waitUntilNoObject(UiObject obj, int second) { + return obj.waitUntilGone(second * 1000); + } + + public void clearLogcat() throws Exception { + Runtime.getRuntime().exec("logcat -c"); + } + + public void waitForLogcatText(String searchText, long timeout) throws Exception { + long startTime = System.currentTimeMillis(); + Process process = Runtime.getRuntime().exec("logcat"); + BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream())); + String line; + + long currentTime = System.currentTimeMillis(); + boolean found = false; + while ((currentTime - startTime) < timeout) { + sleep(2); // poll every two seconds + + while ((line = reader.readLine()) != null) { + if (line.contains(searchText)) { + found = true; + break; + } + } + + if (found) { + break; + } + currentTime = System.currentTimeMillis(); + } + + process.destroy(); + + if ((currentTime - startTime) >= timeout) { + throw new TimeoutException("Timed out waiting for Logcat text \"%s\"".format(searchText)); + } + } + + public void registerWatcher(String name, UiWatcher watcher) { + mDevice.registerWatcher(name, watcher); + } + + public void runWatchers() { + mDevice.runWatchers(); + } + + public void removeWatcher(String name) { + mDevice.removeWatcher(name); + } + + public void setScreenOrientation(ScreenOrientation orientation) throws Exception { + int width = mDevice.getDisplayWidth(); + int height = mDevice.getDisplayHeight(); + switch (orientation) { + case RIGHT: + mDevice.setOrientationRight(); + break; + case NATURAL: + mDevice.setOrientationNatural(); + break; + case LEFT: + mDevice.setOrientationLeft(); + break; + case LANDSCAPE: + if (mDevice.isNaturalOrientation()){ + if (height > width){ + mDevice.setOrientationRight(); + } + } + else { + if (height > width){ + mDevice.setOrientationNatural(); + } + } + break; + case PORTRAIT: + if (mDevice.isNaturalOrientation()){ + if (height < width){ + mDevice.setOrientationRight(); + } + } + else { + if (height < width){ + mDevice.setOrientationNatural(); + } + } + break; + default: + throw new Exception("No orientation specified"); + } + } + + public void unsetScreenOrientation() throws Exception { + mDevice.unfreezeRotation(); + } + + public void uiObjectPerformLongClick(UiObject view, int steps) throws Exception { + Rect rect = view.getBounds(); + mDevice.swipe(rect.centerX(), rect.centerY(), + rect.centerX(), rect.centerY(), steps); + } + + public int getDisplayHeight() { + return mDevice.getDisplayHeight(); + } + + public int getDisplayWidth() { + return mDevice.getDisplayWidth(); + } + + public int getDisplayCentreWidth() { + return getDisplayWidth() / 2; + } + + public int getDisplayCentreHeight() { + return getDisplayHeight() / 2; + } + + public void tapDisplayCentre() { + tapDisplay(getDisplayCentreWidth(), getDisplayCentreHeight()); + } + + public void tapDisplay(int x, int y) { + mDevice.click(x, y); + } + + public void pressEnter() { + mDevice.pressEnter(); + } + + public void pressHome() { + mDevice.pressHome(); + } + + public void pressBack() { + mDevice.pressBack(); + } + + public void uiObjectSwipe(UiObject view, Direction direction, int steps) throws Exception { + switch (direction) { + case UP: + view.swipeUp(steps); + break; + case DOWN: + view.swipeDown(steps); + break; + case LEFT: + view.swipeLeft(steps); + break; + case RIGHT: + view.swipeRight(steps); + break; + case NULL: + throw new Exception("No direction specified"); + default: + break; + } + } + + public void uiDeviceSwipeVertical(int startY, int endY, int xCoordinate, int steps) { + mDevice.swipe(xCoordinate, startY, xCoordinate, endY, steps); + } + + public void uiDeviceSwipeHorizontal(int startX, int endX, int yCoordinate, int steps) { + mDevice.swipe(startX, yCoordinate, endX, yCoordinate, steps); + } + + public void uiObjectVertPinchIn(UiObject view, int steps, int percent) throws Exception { + final int FINGER_TOUCH_HALF_WIDTH = 20; + + // Make value between 1 and 100 + int nPercent = (percent < 0) ? 1 : (percent > 100) ? 100 : percent; + float percentage = nPercent / 100f; + + Rect rect = view.getVisibleBounds(); + + if (rect.width() <= FINGER_TOUCH_HALF_WIDTH * 2) { + throw new IllegalStateException("Object width is too small for operation"); + } + + // Start at the top-center and bottom-center of the control + Point startPoint1 = new Point(rect.centerX(), rect.centerY() + + (int) ((rect.height() / 2) * percentage)); + Point startPoint2 = new Point(rect.centerX(), rect.centerY() + - (int) ((rect.height() / 2) * percentage)); + + // End at the same point at the center of the control + Point endPoint1 = new Point(rect.centerX(), rect.centerY() + FINGER_TOUCH_HALF_WIDTH); + Point endPoint2 = new Point(rect.centerX(), rect.centerY() - FINGER_TOUCH_HALF_WIDTH); + + view.performTwoPointerGesture(startPoint1, startPoint2, endPoint1, endPoint2, steps); + } + + public void uiObjectVertPinchOut(UiObject view, int steps, int percent) throws Exception { + final int FINGER_TOUCH_HALF_WIDTH = 20; + + // Make value between 1 and 100 + int nPercent = (percent < 0) ? 1 : (percent > 100) ? 100 : percent; + float percentage = nPercent / 100f; + + Rect rect = view.getVisibleBounds(); + + if (rect.width() <= FINGER_TOUCH_HALF_WIDTH * 2) { + throw new IllegalStateException("Object width is too small for operation"); + } + + // Start from the same point at the center of the control + Point startPoint1 = new Point(rect.centerX(), rect.centerY() + FINGER_TOUCH_HALF_WIDTH); + Point startPoint2 = new Point(rect.centerX(), rect.centerY() - FINGER_TOUCH_HALF_WIDTH); + + // End at the top-center and bottom-center of the control + Point endPoint1 = new Point(rect.centerX(), rect.centerY() + + (int) ((rect.height() / 2) * percentage)); + Point endPoint2 = new Point(rect.centerX(), rect.centerY() + - (int) ((rect.height() / 2) * percentage)); + + view.performTwoPointerGesture(startPoint1, startPoint2, endPoint1, endPoint2, steps); + } + + public void uiObjectVertPinch(UiObject view, PinchType direction, + int steps, int percent) throws Exception { + if (direction.equals(PinchType.IN)) { + uiObjectVertPinchIn(view, steps, percent); + } else if (direction.equals(PinchType.OUT)) { + uiObjectVertPinchOut(view, steps, percent); + } + } + + public void uiDeviceSwipeUp(int steps) { + mDevice.swipe( + getDisplayCentreWidth(), + (getDisplayCentreHeight() + (getDisplayCentreHeight() / 2)), + getDisplayCentreWidth(), + (getDisplayCentreHeight() / 2), + steps); + } + + public void uiDeviceSwipeDown(int steps) { + mDevice.swipe( + getDisplayCentreWidth(), + (getDisplayCentreHeight() / 2), + getDisplayCentreWidth(), + (getDisplayCentreHeight() + (getDisplayCentreHeight() / 2)), + steps); + } + + public void uiDeviceSwipeLeft(int steps) { + mDevice.swipe( + (getDisplayCentreWidth() + (getDisplayCentreWidth() / 2)), + getDisplayCentreHeight(), + (getDisplayCentreWidth() / 2), + getDisplayCentreHeight(), + steps); + } + + public void uiDeviceSwipeRight(int steps) { + mDevice.swipe( + (getDisplayCentreWidth() / 2), + getDisplayCentreHeight(), + (getDisplayCentreWidth() + (getDisplayCentreWidth() / 2)), + getDisplayCentreHeight(), + steps); + } + + public void uiDeviceSwipe(Direction direction, int steps) throws Exception { + switch (direction) { + case UP: + uiDeviceSwipeUp(steps); + break; + case DOWN: + uiDeviceSwipeDown(steps); + break; + case LEFT: + uiDeviceSwipeLeft(steps); + break; + case RIGHT: + uiDeviceSwipeRight(steps); + break; + case NULL: + throw new Exception("No direction specified"); + default: + break; + } + } + + public void repeatClickUiObject(UiObject view, int repeatCount, int intervalInMillis) throws Exception { + int repeatInterval = intervalInMillis > CLICK_REPEAT_INTERVAL_MINIMUM + ? intervalInMillis : CLICK_REPEAT_INTERVAL_DEFAULT; + if (repeatCount < 1 || !view.isClickable()) { + return; + } + + for (int i = 0; i < repeatCount; ++i) { + view.click(); + SystemClock.sleep(repeatInterval); // in order to register as separate click + } + } + + + public UiObject clickUiObject(FindByCriteria criteria, String matching) throws Exception { + return clickUiObject(criteria, matching, null, false); + } + + public UiObject clickUiObject(FindByCriteria criteria, String matching, boolean wait) throws Exception { + return clickUiObject(criteria, matching, null, wait); + } + + public UiObject clickUiObject(FindByCriteria criteria, String matching, String clazz) throws Exception { + return clickUiObject(criteria, matching, clazz, false); + } + + public UiObject clickUiObject(FindByCriteria criteria, String matching, String clazz, boolean wait) throws Exception { + UiObject view; + + switch (criteria) { + case BY_ID: + view = (clazz == null) + ? getUiObjectByResourceId(matching) : getUiObjectByResourceId(matching, clazz); + break; + case BY_DESC: + view = (clazz == null) + ? getUiObjectByDescription(matching) : getUiObjectByDescription(matching, clazz); + break; + case BY_TEXT: + default: + view = (clazz == null) + ? getUiObjectByText(matching) : getUiObjectByText(matching, clazz); + break; + } + + if (wait) { + view.clickAndWaitForNewWindow(); + } else { + view.click(); + } + return view; + } + + public UiObject getUiObjectByResourceId(String resourceId, String className) throws Exception { + return getUiObjectByResourceId(resourceId, className, uiAutoTimeout); + } + + public UiObject getUiObjectByResourceId(String resourceId, String className, long timeout) throws Exception { + UiObject object = mDevice.findObject(new UiSelector().resourceId(resourceId) + .className(className)); + if (!object.waitForExists(timeout)) { + throw new UiObjectNotFoundException(String.format("Could not find \"%s\" \"%s\"", + resourceId, className)); + } + return object; + } + + public UiObject getUiObjectByResourceId(String id) throws Exception { + UiObject object = mDevice.findObject(new UiSelector().resourceId(id)); + + if (!object.waitForExists(uiAutoTimeout)) { + throw new UiObjectNotFoundException("Could not find view with resource ID: " + id); + } + return object; + } + + public UiObject getUiObjectByDescription(String description, String className) throws Exception { + return getUiObjectByDescription(description, className, uiAutoTimeout); + } + + public UiObject getUiObjectByDescription(String description, String className, long timeout) throws Exception { + UiObject object = mDevice.findObject(new UiSelector().descriptionContains(description) + .className(className)); + if (!object.waitForExists(timeout)) { + throw new UiObjectNotFoundException(String.format("Could not find \"%s\" \"%s\"", + description, className)); + } + return object; + } + + public UiObject getUiObjectByDescription(String desc) throws Exception { + UiObject object = mDevice.findObject(new UiSelector().descriptionContains(desc)); + + if (!object.waitForExists(uiAutoTimeout)) { + throw new UiObjectNotFoundException("Could not find view with description: " + desc); + } + return object; + } + + public UiObject getUiObjectByText(String text, String className) throws Exception { + return getUiObjectByText(text, className, uiAutoTimeout); + } + + public UiObject getUiObjectByText(String text, String className, long timeout) throws Exception { + UiObject object = mDevice.findObject(new UiSelector().textContains(text) + .className(className)); + if (!object.waitForExists(timeout)) { + throw new UiObjectNotFoundException(String.format("Could not find \"%s\" \"%s\"", + text, className)); + } + return object; + } + + public UiObject getUiObjectByText(String text) throws Exception { + UiObject object = mDevice.findObject(new UiSelector().textContains(text)); + + if (!object.waitForExists(uiAutoTimeout)) { + throw new UiObjectNotFoundException("Could not find view with text: " + text); + } + return object; + } + + // Helper to select a folder in the gallery + public void selectGalleryFolder(String directory) throws Exception { + UiObject workdir = + mDevice.findObject(new UiSelector().text(directory) + .className("android.widget.TextView")); + UiScrollable scrollView = + new UiScrollable(new UiSelector().scrollable(true)); + + // If the folder is not present wait for a short time for + // the media server to refresh its index. + boolean discovered = workdir.waitForExists(TimeUnit.SECONDS.toMillis(10)); + if (!discovered && scrollView.exists()) { + // First check if the directory is visible on the first + // screen and if not scroll to the bottom of the screen to look for it. + discovered = scrollView.scrollIntoView(workdir); + + // If still not discovered scroll back to the top of the screen and + // wait for a longer amount of time for the media server to refresh + // its index. + if (!discovered) { + // scrollView.scrollToBeggining() doesn't work for this + // particular scrollable view so use device method instead + for (int i = 0; i < 10; i++) { + uiDeviceSwipeUp(20); + } + discovered = workdir.waitForExists(TimeUnit.SECONDS.toMillis(60)); + + // Scroll to the bottom of the screen one last time + if (!discovered) { + discovered = scrollView.scrollIntoView(workdir); + } + } + } + + if (discovered) { + workdir.clickAndWaitForNewWindow(); + } else { + throw new UiObjectNotFoundException("Could not find folder : " + directory); + } + } + + + // If an an app is not designed for running on the latest version of android + // (currently Q) an additional screen can popup asking to confirm permissions. + public void dismissAndroidPermissionPopup() throws Exception { + UiObject permissionAccess = + mDevice.findObject(new UiSelector().textMatches( + ".*Choose what to allow .* to access")); + UiObject continueButton = + mDevice.findObject(new UiSelector().resourceId("com.android.permissioncontroller:id/continue_button") + .textContains("Continue")); + if (permissionAccess.exists() && continueButton.exists()) { + continueButton.click(); + } + } + + + // If an an app is not designed for running on the latest version of android + // (currently Q) dissmiss the warning popup if present. + public void dismissAndroidVersionPopup() throws Exception { + + // Ensure we have dissmied any permission screens before looking for the version popup + dismissAndroidPermissionPopup(); + + UiObject warningText = + mDevice.findObject(new UiSelector().textContains( + "This app was built for an older version of Android")); + UiObject acceptButton = + mDevice.findObject(new UiSelector().resourceId("android:id/button1") + .className("android.widget.Button")); + if (warningText.exists() && acceptButton.exists()) { + acceptButton.click(); + } + } + + + // If Chrome is a fresh install then these popups may be presented + // dismiss them if visible. + public void dismissChromePopup() throws Exception { + UiObject accept = + mDevice.findObject(new UiSelector().resourceId("com.android.chrome:id/terms_accept") + .className("android.widget.Button")); + if (accept.waitForExists(3000)){ + accept.click(); + UiObject negative = + mDevice.findObject(new UiSelector().resourceId("com.android.chrome:id/negative_button") + .className("android.widget.Button")); + if (negative.waitForExists(10000)) { + negative.click(); + } + } + UiObject lite = + mDevice.findObject(new UiSelector().resourceId("com.android.chrome:id/button_secondary") + .className("android.widget.Button")); + if (lite.exists()){ + lite.click(); + } + } + + // Override getParams function to decode a url encoded parameter bundle before + // passing it to workloads. + public Bundle getParams() { + // Get the original parameter bundle + Bundle parameters = getArguments(); + + // Decode each parameter in the bundle, except null values and "class", as this + // used to control instrumentation and therefore not encoded. + for (String key : parameters.keySet()) { + String param = parameters.getString(key); + if (param != null && !key.equals("class")) { + param = android.net.Uri.decode(param); + parameters = decode(parameters, key, param); + } + } + return parameters; + } + + // Helper function to decode a string and insert it as an appropriate type + // into a provided bundle with its key. + // Each bundle parameter will be a urlencoded string with 2 characters prefixed to the value + // used to store the original type information, e.g. 'fl' -> list of floats. + private Bundle decode(Bundle parameters, String key, String value) { + char value_type = value.charAt(0); + char value_dimension = value.charAt(1); + String param = value.substring(2); + + if (value_dimension == 's') { + if (value_type == 's') { + parameters.putString(key, param); + } else if (value_type == 'f') { + parameters.putFloat(key, Float.parseFloat(param)); + } else if (value_type == 'd') { + parameters.putDouble(key, Double.parseDouble(param)); + } else if (value_type == 'b') { + parameters.putBoolean(key, Boolean.parseBoolean(param)); + } else if (value_type == 'i') { + parameters.putInt(key, Integer.parseInt(param)); + } else if (value_type == 'n') { + parameters.putString(key, "None"); + } else { + throw new IllegalArgumentException("Error decoding:" + key + value + + " - unknown format"); + } + } else if (value_dimension == 'l') { + return decodeArray(parameters, key, value_type, param); + } else { + throw new IllegalArgumentException("Error decoding:" + key + value + + " - unknown format"); + } + return parameters; + } + + // Helper function to deal with decoding arrays and update the bundle with + // an appropriate array type. The string "0newelement0" is used to distinguish + // each element from each other in the array when encoded. + private Bundle decodeArray(Bundle parameters, String key, char type, String value) { + String[] string_list = value.split("0newelement0"); + if (type == 's') { + parameters.putStringArray(key, string_list); + } + else if (type == 'i') { + int[] int_list = new int[string_list.length]; + for (int i = 0; i < string_list.length; i++){ + int_list[i] = Integer.parseInt(string_list[i]); + } + parameters.putIntArray(key, int_list); + } else if (type == 'f') { + float[] float_list = new float[string_list.length]; + for (int i = 0; i < string_list.length; i++){ + float_list[i] = Float.parseFloat(string_list[i]); + } + parameters.putFloatArray(key, float_list); + } else if (type == 'd') { + double[] double_list = new double[string_list.length]; + for (int i = 0; i < string_list.length; i++){ + double_list[i] = Double.parseDouble(string_list[i]); + } + parameters.putDoubleArray(key, double_list); + } else if (type == 'b') { + boolean[] boolean_list = new boolean[string_list.length]; + for (int i = 0; i < string_list.length; i++){ + boolean_list[i] = Boolean.parseBoolean(string_list[i]); + } + parameters.putBooleanArray(key, boolean_list); + } else { + throw new IllegalArgumentException("Error decoding array: " + + value + " - unknown format"); + } + return parameters; + } +} diff --git a/wa/framework/uiauto-androidx/app/src/main/java/com/arm/wa/uiauto/UiAutoUtils.java b/wa/framework/uiauto-androidx/app/src/main/java/com/arm/wa/uiauto/UiAutoUtils.java new file mode 100644 index 00000000..c1674109 --- /dev/null +++ b/wa/framework/uiauto-androidx/app/src/main/java/com/arm/wa/uiauto/UiAutoUtils.java @@ -0,0 +1,35 @@ +/* Copyright 2013-2017 ARM Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +package com.arm.wa.uiauto; + +import android.os.Bundle; + +public final class UiAutoUtils { + + /** Construct launch command of an application. */ + public static String createLaunchCommand(Bundle parameters) { + String launchCommand; + String activityName = parameters.getString("launch_activity"); + String packageName = parameters.getString("package_name"); + if (activityName.equals("None")) { + launchCommand = String.format("am start --user -3 %s", packageName); + } + else { + launchCommand = String.format("am start --user -3 -n %s/%s", packageName, activityName); + } + return launchCommand; + } +} diff --git a/wa/framework/uiauto-androidx/app/src/main/java/com/arm/wa/uiauto/UxPerfUiAutomation.java b/wa/framework/uiauto-androidx/app/src/main/java/com/arm/wa/uiauto/UxPerfUiAutomation.java new file mode 100644 index 00000000..3eac9dc3 --- /dev/null +++ b/wa/framework/uiauto-androidx/app/src/main/java/com/arm/wa/uiauto/UxPerfUiAutomation.java @@ -0,0 +1,55 @@ +/* Copyright 2013-2017 ARM Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +package com.arm.wa.uiauto; + +import android.os.Bundle; + +import java.util.logging.Logger; + +import com.arm.wa.uiauto.BaseUiAutomation.Direction; +import com.arm.wa.uiauto.BaseUiAutomation.PinchType; + + +public class UxPerfUiAutomation { + + private Logger logger = Logger.getLogger(UxPerfUiAutomation.class.getName()); + + public enum GestureType { UIDEVICE_SWIPE, UIOBJECT_SWIPE, PINCH }; + + public static class GestureTestParams { + public GestureType gestureType; + public Direction gestureDirection; + public PinchType pinchType; + public int percent; + public int steps; + + public GestureTestParams(GestureType gesture, Direction direction, int steps) { + this.gestureType = gesture; + this.gestureDirection = direction; + this.pinchType = PinchType.NULL; + this.steps = steps; + this.percent = 0; + } + + public GestureTestParams(GestureType gesture, PinchType pinchType, int steps, int percent) { + this.gestureType = gesture; + this.gestureDirection = Direction.NULL; + this.pinchType = pinchType; + this.steps = steps; + this.percent = percent; + } + } +} diff --git a/wa/framework/uiauto-androidx/build.gradle b/wa/framework/uiauto-androidx/build.gradle new file mode 100644 index 00000000..95f37533 --- /dev/null +++ b/wa/framework/uiauto-androidx/build.gradle @@ -0,0 +1,25 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. + +buildscript { + repositories { + google() + mavenCentral() + } + dependencies { + classpath 'com.android.tools.build:gradle:8.5.0' + + // NOTE: Do not place your application dependencies here; they belong + // in the individual module build.gradle files + } +} + +allprojects { + repositories { + google() + mavenCentral() + } +} + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/wa/framework/uiauto-androidx/build.sh b/wa/framework/uiauto-androidx/build.sh new file mode 100755 index 00000000..1ae5d61a --- /dev/null +++ b/wa/framework/uiauto-androidx/build.sh @@ -0,0 +1,32 @@ +#!/bin/bash +# Copyright 2013-2017 ARM Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +set -e + +# Ensure gradelw exists before starting +if [[ ! -f gradlew ]]; then + echo 'gradlew file not found! Check that you are in the right directory.' + exit 9 +fi + +# Build and return appropriate exit code if failed +./gradlew clean :app:assembleDebug +exit_code=$? +if [ $exit_code -ne 0 ]; then + echo "ERROR: 'gradle build' exited with code $exit_code" + exit $exit_code +fi + +cp app/build/outputs/aar/app-debug.aar ./uiauto.aar diff --git a/wa/framework/uiauto-androidx/gradle.properties b/wa/framework/uiauto-androidx/gradle.properties new file mode 100644 index 00000000..5bac8ac5 --- /dev/null +++ b/wa/framework/uiauto-androidx/gradle.properties @@ -0,0 +1 @@ +android.useAndroidX=true diff --git a/wa/framework/uiauto-androidx/gradle/wrapper/gradle-wrapper.jar b/wa/framework/uiauto-androidx/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000..13372aef Binary files /dev/null and b/wa/framework/uiauto-androidx/gradle/wrapper/gradle-wrapper.jar differ diff --git a/wa/framework/uiauto-androidx/gradle/wrapper/gradle-wrapper.properties b/wa/framework/uiauto-androidx/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..a887ab04 --- /dev/null +++ b/wa/framework/uiauto-androidx/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Wed May 03 15:42:44 BST 2017 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-all.zip diff --git a/wa/framework/uiauto-androidx/gradlew b/wa/framework/uiauto-androidx/gradlew new file mode 100755 index 00000000..9d82f789 --- /dev/null +++ b/wa/framework/uiauto-androidx/gradlew @@ -0,0 +1,160 @@ +#!/usr/bin/env bash + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; +esac + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/wa/framework/uiauto-androidx/gradlew.bat b/wa/framework/uiauto-androidx/gradlew.bat new file mode 100644 index 00000000..8a0b282a --- /dev/null +++ b/wa/framework/uiauto-androidx/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/wa/framework/uiauto-androidx/settings.gradle b/wa/framework/uiauto-androidx/settings.gradle new file mode 100644 index 00000000..e7b4def4 --- /dev/null +++ b/wa/framework/uiauto-androidx/settings.gradle @@ -0,0 +1 @@ +include ':app' diff --git a/wa/framework/uiauto-androidx/uiauto.aar b/wa/framework/uiauto-androidx/uiauto.aar new file mode 100644 index 00000000..7e6a650d Binary files /dev/null and b/wa/framework/uiauto-androidx/uiauto.aar differ diff --git a/wa/framework/uiauto/app/build.gradle b/wa/framework/uiauto/app/build.gradle index e30ae8f6..46dd6df5 100644 --- a/wa/framework/uiauto/app/build.gradle +++ b/wa/framework/uiauto/app/build.gradle @@ -2,7 +2,8 @@ apply plugin: 'com.android.library' android { compileSdkVersion 28 - buildToolsVersion '28.0.3' + // buildToolsVersion is no longer used/accepted. + // buildToolsVersion '28.0.3' defaultConfig { minSdkVersion 18 targetSdkVersion 28 diff --git a/wa/framework/uiauto/build.gradle b/wa/framework/uiauto/build.gradle index 72cd50fc..696c2887 100644 --- a/wa/framework/uiauto/build.gradle +++ b/wa/framework/uiauto/build.gradle @@ -2,8 +2,8 @@ buildscript { repositories { - jcenter() google() + mavenCentral() } dependencies { classpath 'com.android.tools.build:gradle:7.2.1' @@ -16,8 +16,8 @@ buildscript { allprojects { repositories { - jcenter() google() + mavenCentral() } }