Upgrade mobly-snippet-lib to 3c705915cad43acd88a8815b3f3e3cf9455a60a4 am: 23cff8ec53

Original change: https://android-review.googlesource.com/c/platform/external/mobly-snippet-lib/+/3033152

Change-Id: Ia2f9fea19a34a0ce37b49acd40fb778f0bd195c1
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
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"
-  }
-  url {
-    type: GIT
-    value: "https://github.com/google/mobly-snippet-lib"
-  }
-  version: "1.4.0"
-  last_upgrade_date { year: 2023 month: 5 day: 23 }
   license_type: NOTICE
+  last_upgrade_date {
+    year: 2024
+    month: 4
+    day: 9
+  }
+  homepage: "https://github.com/google/mobly-snippet-lib"
+  identifier {
+    type: "Git"
+    value: "https://github.com/google/mobly-snippet-lib"
+    version: "3c705915cad43acd88a8815b3f3e3cf9455a60a4"
+  }
 }
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 @@
         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.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.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 @@
     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 @@
                 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 @@
                 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 @@
 
         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;