Merge remote-tracking branch 'aosp/upstream-master' into mbs-update am: 0572ba0c00 am: d9483212a3

Original change: https://android-review.googlesource.com/c/platform/external/mobly-bundled-snippets/+/2608420

Change-Id: I5870be4890025ea40f9db341f8701ef00d528c45
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/Android.bp b/Android.bp
index 6277a91..a3d1737 100644
--- a/Android.bp
+++ b/Android.bp
@@ -35,6 +35,7 @@
         "gson",
         "guava",
         "mobly-snippet-lib",
+	"androidx.test.uiautomator_uiautomator",
     ],
     srcs: [
         "src/main/**/*.java",
diff --git a/METADATA b/METADATA
index dc73649..51a155a 100644
--- a/METADATA
+++ b/METADATA
@@ -13,7 +13,7 @@
     type: GIT
     value: "https://github.com/google/mobly-bundled-snippets"
   }
-  version: "1ff2867fb8645c5792656bd4b822d70bbce44ec2"
-  last_upgrade_date { year: 2021 month: 12 day: 8 }
+  version: "fbd882fa01fb2134303c697195ab93b739e4ee87"
+  last_upgrade_date { year: 2023 month: 5 day: 31 }
   license_type: NOTICE
 }
diff --git a/build.gradle b/build.gradle
index 82e103d..99839ee 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,10 +1,10 @@
 buildscript {
     repositories {
-        jcenter()
         google()
+        mavenCentral()
     }
     dependencies {
-        classpath 'com.android.tools.build:gradle:4.1.2'
+        classpath 'com.android.tools.build:gradle:7.3.1'
 
         // NOTE: Do not place your application dependencies here.
     }
@@ -17,7 +17,7 @@
 allprojects {
     repositories {
         google()
-        jcenter()
+        mavenCentral()
     }
     gradle.projectsEvaluated {
         tasks.withType(JavaCompile) {
@@ -29,14 +29,12 @@
 apply plugin: 'com.android.application'
 
 android {
-    compileSdkVersion 29
-    buildToolsVersion "30.0.2"
+    compileSdkVersion 31
 
     defaultConfig {
         applicationId "com.google.android.mobly.snippet.bundled"
-        minSdkVersion 15
-        // Set target to 22 to avoid having to deal with runtime permissions.
-        targetSdkVersion 22
+        minSdkVersion 26
+        targetSdkVersion 31
         versionCode 1
         versionName "0.0.1"
         setProperty("archivesBaseName", "mobly-bundled-snippets")
@@ -71,14 +69,16 @@
 }
 
 dependencies {
-    implementation 'androidx.test:runner:1.3.0'
-    implementation 'com.google.android.mobly:mobly-snippet-lib:1.2.0'
+    implementation 'androidx.test:runner:1.5.2'
+    implementation 'com.android.support:multidex:1.0.3'
+    implementation 'com.android.support.test.uiautomator:uiautomator-v18:2.1.3'
+    implementation 'com.google.android.mobly:mobly-snippet-lib:1.4.0'
     implementation 'com.google.code.gson:gson:2.8.6'
-    implementation 'com.google.guava:guava:30.1-jre'
-    implementation 'com.google.errorprone:error_prone_annotations:2.5.1'
+    implementation 'com.google.guava:guava:31.0.1-jre'
+    implementation 'com.google.errorprone:error_prone_annotations:2.15.0'
 
-    testImplementation 'com.google.errorprone:error_prone_annotations:2.5.1'
-    testImplementation 'com.google.guava:guava:30.1-jre'
+    testImplementation 'com.google.errorprone:error_prone_annotations:2.15.0'
+    testImplementation 'com.google.guava:guava:31.0.1-jre'
     testImplementation 'com.google.truth:truth:1.1.2'
     testImplementation 'junit:junit:4.13.2'
 }
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 1ba6cc2..e9e3b96 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -3,4 +3,4 @@
 distributionPath=wrapper/dists
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-6.5.1-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip
diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml
index 795c063..08341c3 100644
--- a/src/main/AndroidManifest.xml
+++ b/src/main/AndroidManifest.xml
@@ -11,7 +11,9 @@
     <uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS" />
     <uses-permission android:name="android.permission.BLUETOOTH" />
     <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
+    <uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
     <uses-permission android:name="android.permission.BLUETOOTH_PRIVILEGED" />
+    <uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
     <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
     <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
     <uses-permission android:name="android.permission.GET_ACCOUNTS" />
diff --git a/src/main/java/com/google/android/mobly/snippet/bundled/AudioSnippet.java b/src/main/java/com/google/android/mobly/snippet/bundled/AudioSnippet.java
index 9b4874f..10a2c07 100644
--- a/src/main/java/com/google/android/mobly/snippet/bundled/AudioSnippet.java
+++ b/src/main/java/com/google/android/mobly/snippet/bundled/AudioSnippet.java
@@ -104,6 +104,24 @@
                 AudioManager.STREAM_VOICE_CALL, value, 0 /* flags, 0 = no flags */);
     }
 
+    @Rpc(description = "Gets the alarm volume.")
+    public Integer getAlarmVolume() {
+        return mAudioManager.getStreamVolume(AudioManager.STREAM_ALARM);
+    }
+
+    @Rpc(description = "Gets the maximum alarm volume value.")
+    public int getAlarmMaxVolume() {
+        return mAudioManager.getStreamMaxVolume(AudioManager.STREAM_ALARM);
+    }
+
+    @Rpc(
+            description =
+                    "Sets the alarm stream volume. The minimum value is 0. Use 'getAlarmMaxVolume'"
+                            + " to determine the maximum.")
+    public void setAlarmVolume(Integer value) {
+        mAudioManager.setStreamVolume(AudioManager.STREAM_ALARM, value, 0 /* flags, 0 = no flags */);
+    }
+
     @Rpc(description = "Silences all audio streams.")
     public void muteAll() throws Exception {
         /* Get numStreams from AudioSystem through reflection. If for some reason this fails,
@@ -129,6 +147,9 @@
         setMusicVolume(0);
     }
 
+    @Rpc(description = "Mute alarm stream.")
+    public void muteAlarm() { setAlarmVolume(0); }
+
     @Override
     public void shutdown() {}
 }
diff --git a/src/main/java/com/google/android/mobly/snippet/bundled/WifiManagerSnippet.java b/src/main/java/com/google/android/mobly/snippet/bundled/WifiManagerSnippet.java
index cf577c3..7e1a416 100644
--- a/src/main/java/com/google/android/mobly/snippet/bundled/WifiManagerSnippet.java
+++ b/src/main/java/com/google/android/mobly/snippet/bundled/WifiManagerSnippet.java
@@ -88,8 +88,18 @@
                 failedConfigs.add(config);
             }
         }
+
+        // If removeNetwork is called on a network with both an open and OWE config, it will remove
+        // both. The subsequent call on the same network will fail. The clear operation may succeed
+        // even if failures appear in the log below.
         if (!failedConfigs.isEmpty()) {
-            throw new WifiManagerSnippetException("Failed to remove networks: " + failedConfigs);
+            Log.e("Encountered error while removing networks: " + failedConfigs);
+        }
+
+        // Re-check configured configs list to ensure that it is cleared
+        unremovedConfigs = mWifiManager.getConfiguredNetworks();
+        if (!unremovedConfigs.isEmpty()) {
+            throw new WifiManagerSnippetException("Failed to remove networks: " + unremovedConfigs);
         }
     }
 
diff --git a/src/main/java/com/google/android/mobly/snippet/bundled/bluetooth/BluetoothAdapterSnippet.java b/src/main/java/com/google/android/mobly/snippet/bundled/bluetooth/BluetoothAdapterSnippet.java
index 6e66e43..c16a2b0 100644
--- a/src/main/java/com/google/android/mobly/snippet/bundled/bluetooth/BluetoothAdapterSnippet.java
+++ b/src/main/java/com/google/android/mobly/snippet/bundled/bluetooth/BluetoothAdapterSnippet.java
@@ -25,6 +25,10 @@
 import android.os.Build;
 import android.os.Bundle;
 import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.uiautomator.By;
+import androidx.test.uiautomator.BySelector;
+import androidx.test.uiautomator.UiDevice;
+import androidx.test.uiautomator.Until;
 import com.google.android.mobly.snippet.Snippet;
 import com.google.android.mobly.snippet.bundled.utils.JsonSerializer;
 import com.google.android.mobly.snippet.bundled.utils.Utils;
@@ -34,6 +38,7 @@
 import java.util.List;
 import java.util.NoSuchElementException;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.regex.Pattern;
 import org.json.JSONException;
 
 /** Snippet class exposing Android APIs in BluetoothAdapter. */
@@ -46,6 +51,10 @@
         public BluetoothAdapterSnippetException(String msg) {
             super(msg);
         }
+
+        public BluetoothAdapterSnippetException(String msg, Throwable err) {
+            super(msg, err);
+        }
     }
 
     // Timeout to measure consistent BT state.
@@ -93,6 +102,16 @@
         return null;
     }
 
+    /* Gets the UiDevice instance for UI operations. */
+    private static UiDevice getUiDevice() throws BluetoothAdapterSnippetException {
+        try {
+            return UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+        } catch (IllegalStateException e) {
+            throw new BluetoothAdapterSnippetException("Failed to get UiDevice. Please ensure that "
+                    + "no other UiAutomation service is running.", e);
+        }
+    }
+
     @Rpc(description = "Enable bluetooth with a 30s timeout.")
     public void btEnable() throws BluetoothAdapterSnippetException, InterruptedException {
         if (mBluetoothAdapter.getState() == BluetoothAdapter.STATE_ON) {
@@ -100,7 +119,19 @@
         }
         waitForStableBtState();
 
-        if (!mBluetoothAdapter.enable()) {
+        if (Build.VERSION.SDK_INT >= 33) {
+            // BluetoothAdapter#enable is removed from public SDK for 33 and above, so uses an
+            // intent instead.
+            UiDevice uiDevice = getUiDevice();
+            Intent enableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
+            enableIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+            // Triggers the system UI popup to ask for explicit permission.
+            mContext.startActivity(enableIntent);
+            // Clicks the "ALLOW" button.
+            BySelector allowButtonSelector = By.text(TEXT_PATTERN_ALLOW).clickable(true);
+            uiDevice.wait(Until.findObject(allowButtonSelector), 10);
+            uiDevice.findObject(allowButtonSelector).click();
+        } else if (!mBluetoothAdapter.enable()) {
             throw new BluetoothAdapterSnippetException("Failed to start enabling bluetooth.");
         }
         if (!Utils.waitUntil(
@@ -200,7 +231,20 @@
             throw new BluetoothAdapterSnippetException(
                     "Bluetooth is not enabled, cannot become discoverable.");
         }
-        if (Build.VERSION.SDK_INT > 29) {
+        if (Build.VERSION.SDK_INT >= 31) {
+            // BluetoothAdapter#setScanMode is removed from public SDK for 31 and above, so uses an
+            // intent instead.
+            UiDevice uiDevice = getUiDevice();
+            Intent discoverableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
+            discoverableIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+            discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, duration);
+            // Triggers the system UI popup to ask for explicit permission.
+            mContext.startActivity(discoverableIntent);
+            // Clicks the "ALLOW" button.
+            BySelector allowButtonSelector = By.text(TEXT_PATTERN_ALLOW).clickable(true);
+            uiDevice.wait(Until.findObject(allowButtonSelector), 10);
+            uiDevice.findObject(allowButtonSelector).click();
+        } else if (Build.VERSION.SDK_INT >= 30) {
             if (!(boolean)
                     Utils.invokeByReflection(
                             mBluetoothAdapter,
@@ -221,6 +265,9 @@
         }
     }
 
+    private static final Pattern TEXT_PATTERN_ALLOW =
+            Pattern.compile("allow", Pattern.CASE_INSENSITIVE);
+
     @Rpc(description = "Cancel ongoing bluetooth discovery.")
     public void btCancelDiscovery() throws BluetoothAdapterSnippetException {
         if (!mBluetoothAdapter.isDiscovering()) {
@@ -305,7 +352,7 @@
                 return;
             }
         }
-        throw new NoSuchElementException("No device wih address " + deviceAddress + " is paired.");
+        throw new NoSuchElementException("No device with address " + deviceAddress + " is paired.");
     }
 
     @Override
diff --git a/src/main/java/com/google/android/mobly/snippet/bundled/utils/JsonSerializer.java b/src/main/java/com/google/android/mobly/snippet/bundled/utils/JsonSerializer.java
index 82e1e4f..82e6f7c 100644
--- a/src/main/java/com/google/android/mobly/snippet/bundled/utils/JsonSerializer.java
+++ b/src/main/java/com/google/android/mobly/snippet/bundled/utils/JsonSerializer.java
@@ -27,6 +27,7 @@
 import android.os.Build;
 import android.os.Bundle;
 import android.os.ParcelUuid;
+import android.util.SparseArray;
 import com.google.gson.Gson;
 import com.google.gson.GsonBuilder;
 import java.lang.reflect.Modifier;
@@ -41,17 +42,13 @@
  * A collection of methods used to serialize data types defined in Android API into JSON strings.
  */
 public class JsonSerializer {
-    private static Gson mGson;
-
-    public JsonSerializer() {
-        GsonBuilder builder = new GsonBuilder();
-        mGson =
-                builder.serializeNulls()
-                        .excludeFieldsWithModifiers(Modifier.STATIC)
-                        .enableComplexMapKeySerialization()
-                        .disableInnerClassSerialization()
-                        .create();
-    }
+    private static final Gson gson =
+        new GsonBuilder()
+            .serializeNulls()
+            .excludeFieldsWithModifiers(Modifier.STATIC)
+            .enableComplexMapKeySerialization()
+            .disableInnerClassSerialization()
+            .create();
 
     /**
      * Remove the extra quotation marks from the beginning and the end of a string.
@@ -89,11 +86,11 @@
      * @throws JSONException
      */
     private JSONObject defaultSerialization(Object data) throws JSONException {
-        return new JSONObject(mGson.toJson(data));
+        return new JSONObject(gson.toJson(data));
     }
 
     private JSONObject serializeDhcpInfo(DhcpInfo data) throws JSONException {
-        JSONObject result = new JSONObject(mGson.toJson(data));
+        JSONObject result = new JSONObject(gson.toJson(data));
         int ipAddress = data.ipAddress;
         byte[] addressBytes = {
             (byte) (0xff & ipAddress),
@@ -111,14 +108,14 @@
     }
 
     private JSONObject serializeWifiConfiguration(WifiConfiguration data) throws JSONException {
-        JSONObject result = new JSONObject(mGson.toJson(data));
+        JSONObject result = new JSONObject(gson.toJson(data));
         result.put("Status", WifiConfiguration.Status.strings[data.status]);
         result.put("SSID", trimQuotationMarks(data.SSID));
         return result;
     }
 
     private JSONObject serializeWifiInfo(WifiInfo data) throws JSONException {
-        JSONObject result = new JSONObject(mGson.toJson(data));
+        JSONObject result = new JSONObject(gson.toJson(data));
         result.put("SSID", trimQuotationMarks(data.getSSID()));
         for (SupplicantState state : SupplicantState.values()) {
             if (data.getSupplicantState().equals(state)) {
@@ -187,6 +184,20 @@
         Bundle result = new Bundle();
         result.putString("DeviceName", record.getDeviceName());
         result.putInt("TxPowerLevel", record.getTxPowerLevel());
+        result.putBundle(
+            "manufacturerSpecificData", serializeBleScanManufacturerSpecificData(record));
+        return result;
+    }
+
+    /** Serialize manufacturer specific data from ScanRecord for Bluetooth LE. */
+    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+    private Bundle serializeBleScanManufacturerSpecificData(ScanRecord record) {
+        Bundle result = new Bundle();
+        SparseArray<byte[]> sparseArray = record.getManufacturerSpecificData();
+        for (int i = 0; i < sparseArray.size(); i++) {
+            int key = sparseArray.keyAt(i);
+            result.putByteArray(String.valueOf(key), sparseArray.get(key));
+        }
         return result;
     }
 
diff --git a/src/main/java/com/google/android/mobly/snippet/bundled/utils/RpcEnum.java b/src/main/java/com/google/android/mobly/snippet/bundled/utils/RpcEnum.java
index d3d95ae..d6442a8 100644
--- a/src/main/java/com/google/android/mobly/snippet/bundled/utils/RpcEnum.java
+++ b/src/main/java/com/google/android/mobly/snippet/bundled/utils/RpcEnum.java
@@ -29,8 +29,8 @@
 public class RpcEnum {
     private final ImmutableBiMap<String, Integer> mEnums;
 
-    private RpcEnum(ImmutableBiMap.Builder<String, Integer> builder, int minSdk) {
-        mEnums = builder.build();
+    private RpcEnum(ImmutableBiMap.Builder<String, Integer> builder) {
+        mEnums = builder.buildOrThrow();
     }
 
     /**
@@ -64,7 +64,6 @@
     /** Builder for RpcEnum. */
     public static class Builder {
         private final ImmutableBiMap.Builder<String, Integer> builder;
-        public int minSdk = 0;
 
         public Builder() {
             builder = new ImmutableBiMap.Builder<>();
@@ -83,7 +82,7 @@
         }
 
         public RpcEnum build() {
-            return new RpcEnum(builder, minSdk);
+            return new RpcEnum(builder);
         }
     }
 }