aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorZiwei Zhang <zhangxh@google.com>2024-04-09 12:49:28 +0800
committerZiwei Zhang <zhangxh@google.com>2024-04-09 05:01:40 +0000
commit23cff8ec536cad6ee60e68d4cbfa338c20571294 (patch)
tree38533c9648bef596a95c5fa85774a4946e992625
parenta3af46165c6d4168bb33d6e97b9959eba531b5bd (diff)
parent3c705915cad43acd88a8815b3f3e3cf9455a60a4 (diff)
downloadmobly-snippet-lib-master.tar.gz
Upgrade mobly-snippet-lib to 3c705915cad43acd88a8815b3f3e3cf9455a60a4HEADmastermain
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 Test: TreeHugger Bug: 331669033 Change-Id: I9c9338bbc1bbadf87f96381a366ba1f977965572
-rw-r--r--METADATA25
-rw-r--r--examples/ex4_uiautomator/README.md61
-rw-r--r--examples/ex4_uiautomator/build.gradle32
-rw-r--r--examples/ex4_uiautomator/src/main/AndroidManifest.xml19
-rw-r--r--examples/ex4_uiautomator/src/main/java/com/google/android/mobly/snippet/example4/UiAutomatorSnippet.java111
-rw-r--r--third_party/sl4a/src/main/java/com/google/android/mobly/snippet/rpc/JsonBuilder.java3
-rw-r--r--third_party/sl4a/src/main/java/com/google/android/mobly/snippet/rpc/MethodDescriptor.java24
-rw-r--r--third_party/sl4a/src/main/java/com/google/android/mobly/snippet/rpc/SimpleServer.java3
8 files changed, 69 insertions, 209 deletions
diff --git a/METADATA b/METADATA
index 7318c3b..17e78eb 100644
--- a/METADATA
+++ b/METADATA
@@ -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;