diff options
author | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2024-04-16 23:06:56 +0000 |
---|---|---|
committer | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2024-04-16 23:06:56 +0000 |
commit | 4faef8634d6a92213e81e33230012c3dcc46509f (patch) | |
tree | 38533c9648bef596a95c5fa85774a4946e992625 | |
parent | 1350bb799d4791fa8c1b750b740949d796e12258 (diff) | |
parent | 23cff8ec536cad6ee60e68d4cbfa338c20571294 (diff) | |
download | mobly-snippet-lib-sdk-release.tar.gz |
Snap for 11724015 from 23cff8ec536cad6ee60e68d4cbfa338c20571294 to sdk-releasesdk-release
Change-Id: I1da38e5ebd52634ccdd88c4d5c765b61b7c07073
8 files changed, 69 insertions, 209 deletions
@@ -1,17 +1,20 @@ -name: "mobly-snippet-lib" -description: - "Mobly Snippet Lib is a library for triggering device-side code from host-side Mobly tests." +# This project was upgraded with external_updater. +# Usage: tools/external_updater/updater.sh update external/mobly-snippet-lib +# For more info, check https://cs.android.com/android/platform/superproject/+/main:tools/external_updater/README.md +name: "mobly-snippet-lib" +description: "Mobly Snippet Lib is a library for triggering device-side code from host-side Mobly tests." third_party { - url { - type: HOMEPAGE - value: "https://github.com/google/mobly-snippet-lib" + license_type: NOTICE + last_upgrade_date { + year: 2024 + month: 4 + day: 9 } - url { - type: GIT + homepage: "https://github.com/google/mobly-snippet-lib" + identifier { + type: "Git" value: "https://github.com/google/mobly-snippet-lib" + version: "3c705915cad43acd88a8815b3f3e3cf9455a60a4" } - version: "1.4.0" - last_upgrade_date { year: 2023 month: 5 day: 23 } - license_type: NOTICE } diff --git a/examples/ex4_uiautomator/README.md b/examples/ex4_uiautomator/README.md index 10fb144..5210333 100644 --- a/examples/ex4_uiautomator/README.md +++ b/examples/ex4_uiautomator/README.md @@ -1,45 +1,36 @@ # UIAutomator Snippet Example -This example shows you how to create snippets that control the UI of a device -across system and multiple app views using UIAutomator. Unlike Espresso-based -UI automation, it does not require access to app source code. +The UiAutomator API, which allows developers to automate UI interactions on an +Android device, is now available in Python on +[google/snippet-uiautomator](https://github.com/google/snippet-uiautomator). +This makes it possible for developers to use UiAutomator in Mobly tests without +having to write Java. -This snippet is written as a [standalone snippet](../ex1_standalone_app/README.md) -and does not target another app. In particular, it doesn't need to target the -app under test, so it doesn't need its classpath or to be signed with the same -key. +The `snippet-uiautomator` package is a wrapper around the AndroidX UiAutomator +APIs. It provides a Pythonic interface for interacting with Android UI elements, +such as finding and clicking on buttons, entering text into fields, and +scrolling through lists. -See the [Espresso snippet tutorial](../ex2_espresso/README.md) for more -information about the app this example automates. +To use the `snippet-uiautomator` package, developers simply need to install it +from PyPI and import it into their Python code. Once imported, they can use the +package's API to automate any UI interaction. -## Running the example code +Here is an example of how to use the `snippet-uiautomator` package to automate a +simple UI interaction: -This folder contains a fully working example of a snippet apk that uses -UIAutomator to automate a simple app. +```Python +from mobly.controllers import android_device +from snippet_uiautomator import uiautomator -1. Compile the main app and automation. The main app of ex2 (espresso) is used - as the app to automate. Unlike espresso, the uiautomator test does not - depend on this apk and does not use its source or classpath, so you must - compile and install the app separately. +# Connect to an Android device. +ad = android_device.AndroidDevice(serial) - ./gradlew examples:ex2_espresso:assembleDebug examples:ex4_uiautomator:assembleDebug +# Load UiAutomator service. +uiautomator.load_uiautomator_service(ad) -1. Install the apks on your phone +# Find the "Login" button. +button = ad.ui(res='com.example.app:id/login_button') - adb install -r ./examples/ex2_espresso/build/outputs/apk/debug/ex2_espresso-main-debug.apk - adb install -r ./examples/ex4_uiautomator/build/outputs/apk/debug/ex4_uiautomator-debug.apk - -1. Use `snippet_shell` from mobly to trigger `pushMainButton()`: - - snippet_shell.py com.google.android.mobly.snippet.example4 - - >>> print(s.help()) - Known methods: - pushMainButton(boolean) returns void // Pushes the main app button, and checks the label if this is the first time. - startMainActivity() returns void // Opens the main activity of the app - uiautomatorDump() returns String // Perform a UIAutomator dump - - >>> s.startMainActivity() - >>> s.pushMainButton(True) - -1. Press ctrl+d to exit the shell and terminate the app. +# Click on the button. +button.click() +``` diff --git a/examples/ex4_uiautomator/build.gradle b/examples/ex4_uiautomator/build.gradle deleted file mode 100644 index 24188d9..0000000 --- a/examples/ex4_uiautomator/build.gradle +++ /dev/null @@ -1,32 +0,0 @@ -apply plugin: 'com.android.application' - -android { - // This has to match what the appcompat dep expects. - compileSdkVersion 31 - - defaultConfig { - applicationId "com.google.android.mobly.snippet.example4" - minSdkVersion 26 - targetSdkVersion 31 - versionCode 1 - versionName "0.0.2" - } - lintOptions { - abortOnError false - checkAllWarnings true - warningsAsErrors true - disable 'HardwareIds','MissingApplicationIcon','GoogleAppIndexingWarning','InvalidPackage','OldTargetApi' - } -} - -dependencies { - // The 'compile project' dep is to compile against the snippet lib source in - // this repo. For your own snippets, you'll want to use the regular - // 'compile' dep instead: - //compile 'com.google.android.mobly:mobly-snippet-lib:1.4.0' - implementation project(':mobly-snippet-lib') - implementation 'junit:junit:4.13.2' - implementation 'androidx.test:runner:1.4.0' - implementation 'androidx.appcompat:appcompat:1.4.0-beta01' - implementation 'androidx.test.uiautomator:uiautomator:2.2.0' -} diff --git a/examples/ex4_uiautomator/src/main/AndroidManifest.xml b/examples/ex4_uiautomator/src/main/AndroidManifest.xml deleted file mode 100644 index 89d5276..0000000 --- a/examples/ex4_uiautomator/src/main/AndroidManifest.xml +++ /dev/null @@ -1,19 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest - xmlns:android="http://schemas.android.com/apk/res/android" - package="com.google.android.mobly.snippet.example4"> - - <application android:allowBackup="false"> - <meta-data - android:name="mobly-snippets" - android:value="com.google.android.mobly.snippet.example4.UiAutomatorSnippet" /> - </application> - - <!-- This snippet does NOT target ex2 (which is the main app the code - automates). The instrumentation target is itself which creates a - standalone snippet. --> - <instrumentation - android:name="com.google.android.mobly.snippet.SnippetRunner" - android:targetPackage="com.google.android.mobly.snippet.example4" /> - -</manifest> diff --git a/examples/ex4_uiautomator/src/main/java/com/google/android/mobly/snippet/example4/UiAutomatorSnippet.java b/examples/ex4_uiautomator/src/main/java/com/google/android/mobly/snippet/example4/UiAutomatorSnippet.java deleted file mode 100644 index 9fc01b2..0000000 --- a/examples/ex4_uiautomator/src/main/java/com/google/android/mobly/snippet/example4/UiAutomatorSnippet.java +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright (C) 2017 Google Inc. - * - * 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.google.android.mobly.snippet.example4; - -import static org.junit.Assert.assertEquals; - -import android.content.Context; -import android.content.Intent; - -import androidx.test.platform.app.InstrumentationRegistry; -import androidx.test.uiautomator.By; -import androidx.test.uiautomator.UiDevice; -import androidx.test.uiautomator.UiObject2; -import androidx.test.uiautomator.Until; -import com.google.android.mobly.snippet.Snippet; -import com.google.android.mobly.snippet.rpc.Rpc; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.nio.charset.Charset; - -/** - * Demonstrates how to drive an app using UIAutomator without access to the app's source code or - * classpath. - * - * <p>Drives the Espresso example app from ex2 without instrumenting it. - */ -public class UiAutomatorSnippet implements Snippet { - private static final class UiAutomatorSnippetException extends Exception { - private static final long serialVersionUID = 1; - - public UiAutomatorSnippetException(String message) { - super(message); - } - } - - private static final String MAIN_PACKAGE = "com.google.android.mobly.snippet.example2"; - private static final int LAUNCH_TIMEOUT = 5000; - - private final Context mContext; - private final UiDevice mDevice; - - public UiAutomatorSnippet() { - mContext = InstrumentationRegistry.getInstrumentation().getContext(); - mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()); - } - - @Rpc(description="Opens the main activity of the app") - public void startMainActivity() throws UiAutomatorSnippetException { - // Send the launch intent - Intent intent = mContext.getPackageManager().getLaunchIntentForPackage(MAIN_PACKAGE); - if (intent == null) { - throw new UiAutomatorSnippetException( - "Unable to create launch intent for " + MAIN_PACKAGE + "; is the app installed?"); - } - intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); - mContext.startActivity(intent); - - // Wait for the app to appear - mDevice.wait(Until.hasObject(By.pkg(MAIN_PACKAGE).depth(0)), LAUNCH_TIMEOUT); - } - - @Rpc(description="Pushes the main app button, and checks the label if this is the first time.") - public void pushMainButton(boolean checkFirstRun) { - if (checkFirstRun) { - assertEquals( - "Hello World!", - // Example of finding object by id. - mDevice.findObject(By.res(MAIN_PACKAGE, "main_text_view")).getText()); - } - // Example of finding a button by text. Finding by ID is also possible, as above. - UiObject2 button = mDevice.findObject(By.text("PUSH THE BUTTON!")); - button.click(); - if (checkFirstRun) { - assertEquals( - "Button pressed 1 times", - mDevice.findObject(By.res(MAIN_PACKAGE, "main_text_view")).getText()); - } - } - - @Rpc(description="Perform a UIAutomator dump") - public String uiautomatorDump() throws IOException { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - try { - mDevice.dumpWindowHierarchy(baos); - byte[] dumpBytes = baos.toByteArray(); - String dumpStr = new String(dumpBytes, Charset.forName("UTF-8")); - return dumpStr; - } finally { - baos.close(); - } - } - - @Override - public void shutdown() throws IOException { - mDevice.executeShellCommand("am force-stop " + MAIN_PACKAGE); - } -} diff --git a/third_party/sl4a/src/main/java/com/google/android/mobly/snippet/rpc/JsonBuilder.java b/third_party/sl4a/src/main/java/com/google/android/mobly/snippet/rpc/JsonBuilder.java index f543b62..f996fce 100644 --- a/third_party/sl4a/src/main/java/com/google/android/mobly/snippet/rpc/JsonBuilder.java +++ b/third_party/sl4a/src/main/java/com/google/android/mobly/snippet/rpc/JsonBuilder.java @@ -40,6 +40,9 @@ public class JsonBuilder { if (data == null) { return JSONObject.NULL; } + if (data instanceof Byte) { + return (Byte) data & 0xFF; + } if (data instanceof Integer) { return data; } diff --git a/third_party/sl4a/src/main/java/com/google/android/mobly/snippet/rpc/MethodDescriptor.java b/third_party/sl4a/src/main/java/com/google/android/mobly/snippet/rpc/MethodDescriptor.java index 214ffe7..6b66e2b 100644 --- a/third_party/sl4a/src/main/java/com/google/android/mobly/snippet/rpc/MethodDescriptor.java +++ b/third_party/sl4a/src/main/java/com/google/android/mobly/snippet/rpc/MethodDescriptor.java @@ -23,7 +23,9 @@ import com.google.android.mobly.snippet.manager.SnippetManager; import com.google.android.mobly.snippet.manager.SnippetObjectConverterManager; import com.google.android.mobly.snippet.util.AndroidUtil; import java.lang.annotation.Annotation; +import java.lang.reflect.Array; import java.lang.reflect.Constructor; +import java.lang.reflect.GenericArrayType; import java.lang.reflect.Method; import java.lang.reflect.Type; import java.util.ArrayList; @@ -33,6 +35,7 @@ import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.stream.IntStream; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; @@ -105,6 +108,14 @@ public final class MethodDescriptor { private static Object convertParameter(final JSONArray parameters, int index, Type type) throws JSONException, RpcError { try { + // The refelection system sometimes returns a GenericArrayType type + // instead of a raw type, causing issues with later type + // comparisons. + if (type instanceof GenericArrayType) { + Type componentType = ((GenericArrayType) type).getGenericComponentType(); + type = Array.newInstance((Class<?>) componentType, 0).getClass(); + } + // We must handle null and numbers explicitly because we cannot magically cast them. We // also need to convert implicitly from numbers to bools. if (parameters.isNull(index)) { @@ -131,6 +142,9 @@ public final class MethodDescriptor { for (int i = 0; i < list.length(); i++) { result[i] = list.getInt(i); } + if (type == int[].class) { + return Arrays.stream(result).mapToInt(Integer::intValue).toArray(); + } return result; } else if (type == Long[].class || type == long[].class) { JSONArray list = parameters.getJSONArray(index); @@ -138,13 +152,21 @@ public final class MethodDescriptor { for (int i = 0; i < list.length(); i++) { result[i] = list.getLong(i); } + if (type == long[].class) { + return Arrays.stream(result).mapToLong(Long::longValue).toArray(); + } return result; - } else if (type == Byte.class || type == byte[].class) { + } else if (type == Byte[].class || type == byte[].class) { JSONArray list = parameters.getJSONArray(index); byte[] result = new byte[list.length()]; for (int i = 0; i < list.length(); i++) { result[i] = (byte) list.getInt(i); } + if (type == Byte[].class) { + return IntStream.range(0, result.length) + .mapToObj(i -> result[i]) + .toArray(Byte[]::new); + } return result; } else if (type == String[].class) { JSONArray list = parameters.getJSONArray(index); diff --git a/third_party/sl4a/src/main/java/com/google/android/mobly/snippet/rpc/SimpleServer.java b/third_party/sl4a/src/main/java/com/google/android/mobly/snippet/rpc/SimpleServer.java index db7255a..54db8ae 100644 --- a/third_party/sl4a/src/main/java/com/google/android/mobly/snippet/rpc/SimpleServer.java +++ b/third_party/sl4a/src/main/java/com/google/android/mobly/snippet/rpc/SimpleServer.java @@ -138,6 +138,9 @@ public abstract class SimpleServer { InetAddress candidate = null; Enumeration<NetworkInterface> nets = NetworkInterface.getNetworkInterfaces(); + if (nets == null) { + return InetAddress.getLocalHost(); // Return local host if no interfaces found. + } for (NetworkInterface netint : Collections.list(nets)) { if (!netint.isLoopback() || !netint.isUp()) { // Ignore if localhost or not active continue; |