Spectatio supports hasAncestor from UI Automator

Test: Locally
Bug: 280643782
Change-Id: I5f8dfe374db403b9dac24d3717b50a341550a9d7
diff --git a/libraries/app-helpers/spectatio/spectatio-util/src/android/platform/spectatio/configs/UiElement.java b/libraries/app-helpers/spectatio/spectatio-util/src/android/platform/spectatio/configs/UiElement.java
index 6cc5133..980506a 100644
--- a/libraries/app-helpers/spectatio/spectatio-util/src/android/platform/spectatio/configs/UiElement.java
+++ b/libraries/app-helpers/spectatio/spectatio-util/src/android/platform/spectatio/configs/UiElement.java
@@ -59,7 +59,11 @@
     @SerializedName("SPECIFIERS")
     private List<UiElement> mSpecifiers;
 
-    // The specifier for the child of a HAS_CHILD specifier
+    // The specifier for the parent of a HAS_ASCENDANT specifier
+    @SerializedName("ANCESTOR")
+    private UiElement mAncestor;
+
+    // The specifier for the child of a HAS_DESCENDANT specifier
     @SerializedName("DESCENDANT")
     private UiElement mDescendant;
 
@@ -79,9 +83,19 @@
         mSpecifiers = specifiers;
     }
 
-    public UiElement(String type, UiElement descendant, int maxDepth) {
+    public UiElement(String type, UiElement relative, int maxDepth) {
         mType = type;
-        mDescendant = descendant;
+        switch (type) {
+            case JsonConfigConstants.HAS_DESCENDANT:
+                mDescendant = relative;
+                break;
+            case JsonConfigConstants.HAS_ANCESTOR:
+                mAncestor = relative;
+                break;
+            default:
+                throw new RuntimeException(
+                        "Unrecognized type given to UiElement constructor with relative argument");
+        }
         mMaxDepth = maxDepth;
     }
 
@@ -119,6 +133,8 @@
                     return By.clazz(mPackage, mValue);
                 }
                 return By.clazz(mValue);
+            case JsonConfigConstants.HAS_ANCESTOR:
+                return By.hasAncestor(mAncestor.getBySelectorForUiElement(), mMaxDepth);
             case JsonConfigConstants.HAS_DESCENDANT:
                 return By.hasDescendant(mDescendant.getBySelectorForUiElement(), mMaxDepth);
             case JsonConfigConstants.MULTIPLE:
@@ -165,6 +181,8 @@
                 }
                 s.clazz(mValue);
                 break;
+            case JsonConfigConstants.HAS_ANCESTOR:
+                s.hasAncestor(mAncestor.getBySelectorForUiElement(), mMaxDepth);
             case JsonConfigConstants.HAS_DESCENDANT:
                 s.hasDescendant(mDescendant.getBySelectorForUiElement(), mMaxDepth);
                 break;
diff --git a/libraries/app-helpers/spectatio/spectatio-util/src/android/platform/spectatio/configs/validators/ValidateUiElement.java b/libraries/app-helpers/spectatio/spectatio-util/src/android/platform/spectatio/configs/validators/ValidateUiElement.java
index c695400..563e8d4 100644
--- a/libraries/app-helpers/spectatio/spectatio-util/src/android/platform/spectatio/configs/validators/ValidateUiElement.java
+++ b/libraries/app-helpers/spectatio/spectatio-util/src/android/platform/spectatio/configs/validators/ValidateUiElement.java
@@ -30,7 +30,6 @@
 import java.util.List;
 import java.util.Map.Entry;
 import java.util.Set;
-import java.util.stream.Collectors;
 
 /**
  * {@link ValidateUiElement} is a deserializer that validates Ui Elements in Spectatio JSON Config
@@ -48,6 +47,7 @@
                     JsonConfigConstants.TEXT_CONTAINS,
                     JsonConfigConstants.DESCRIPTION,
                     JsonConfigConstants.CLASS,
+                    JsonConfigConstants.HAS_ANCESTOR,
                     JsonConfigConstants.HAS_DESCENDANT,
                     JsonConfigConstants.MULTIPLE,
                     JsonConfigConstants.RESOURCE_ID);
@@ -59,6 +59,7 @@
                     JsonConfigConstants.PACKAGE,
                     JsonConfigConstants.FLAG,
                     JsonConfigConstants.MAX_DEPTH,
+                    JsonConfigConstants.ANCESTOR,
                     JsonConfigConstants.DESCENDANT,
                     JsonConfigConstants.SPECIFIERS);
 
@@ -88,15 +89,34 @@
                     specifiersJson.asList().stream()
                             .map(element -> context.<UiElement>deserialize(element, typeOfT))
                             .toList();
+
+            int ancestorSpecifiers = 0;
             for (UiElement specifier : specifiers) {
                 if (JsonConfigConstants.MULTIPLE.equals(specifier.getType())) {
                     throw new RuntimeException(
                             "Multiple-specifier can't contain a multiple-specifier.");
                 }
+                if (JsonConfigConstants.HAS_ANCESTOR.equals(specifier.getType())) {
+                    ancestorSpecifiers++;
+                    if (ancestorSpecifiers > 1) {
+                        throw new RuntimeException(
+                                "Multiple-specifier can't contain more than one ancestor "
+                                        + "specifier.");
+                    }
+                }
             }
             return new UiElement(specifiers);
         }
 
+        if (JsonConfigConstants.HAS_ANCESTOR.equals(type)) {
+            JsonObject parent = validateAndGetObject(JsonConfigConstants.ANCESTOR, jsonObject);
+            int maxDepth = validateAndGetInteger(JsonConfigConstants.MAX_DEPTH, jsonObject, 1);
+            return new UiElement(
+                    JsonConfigConstants.HAS_ANCESTOR,
+                    context.deserialize(parent, typeOfT),
+                    maxDepth);
+        }
+
         if (JsonConfigConstants.HAS_DESCENDANT.equals(type)) {
             JsonObject childJson = validateAndGetObject(JsonConfigConstants.DESCENDANT, jsonObject);
             int maxDepth = validateAndGetInteger(JsonConfigConstants.MAX_DEPTH, jsonObject, 1);
@@ -236,7 +256,7 @@
                     String.format(
                             "UI Element TYPE %s in Spectatio JSON Config is invalid. Supported"
                                 + " Types: [ RESOURCE_ID, TEXT, TEXT_CONTAINS, DESCRIPTION, CLASS,"
-                                + " MULTIPLE ]",
+                                + " MULTIPLE, HAS_ANCESTOR, HAS_DESCENDANT ]",
                             type));
         }
     }
@@ -247,13 +267,12 @@
                         .map(Entry::getKey)
                         .map(String::trim)
                         .filter(key -> !mSupportedProperties.contains(key))
-                        .collect(Collectors.toList());
+                        .toList();
         if (!unknownProperties.isEmpty()) {
             throw new RuntimeException(
                     String.format(
                             "Unknown properties: [ %s ] for %s in Spectatio JSON Config",
-                            unknownProperties.stream().collect(Collectors.joining(", ")),
-                            jsonObject));
+                            String.join(", ", unknownProperties), jsonObject));
         }
     }
 }
diff --git a/libraries/app-helpers/spectatio/spectatio-util/src/android/platform/spectatio/constants/JsonConfigConstants.java b/libraries/app-helpers/spectatio/spectatio-util/src/android/platform/spectatio/constants/JsonConfigConstants.java
index 845caa3..75c9833 100644
--- a/libraries/app-helpers/spectatio/spectatio-util/src/android/platform/spectatio/constants/JsonConfigConstants.java
+++ b/libraries/app-helpers/spectatio/spectatio-util/src/android/platform/spectatio/constants/JsonConfigConstants.java
@@ -29,6 +29,7 @@
     public static final String VALUE = "VALUE";
     public static final String FLAG = "FLAG";
     public static final String PACKAGE = "PACKAGE";
+    public static final String ANCESTOR = "ANCESTOR";
     public static final String DESCENDANT = "DESCENDANT";
     public static final String MAX_DEPTH = "MAX_DEPTH";
     public static final String SPECIFIERS = "SPECIFIERS";
@@ -40,6 +41,7 @@
     public static final String TEXT_CONTAINS = "TEXT_CONTAINS";
     public static final String DESCRIPTION = "DESCRIPTION";
     public static final String CLASS = "CLASS";
+    public static final String HAS_ANCESTOR = "HAS_ANCESTOR";
     public static final String HAS_DESCENDANT = "HAS_DESCENDANT";
     public static final String MULTIPLE = "MULTIPLE";