1 /*
2  * Copyright (C) 2022 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.platform.spectatio.configs;
18 
19 import android.platform.spectatio.constants.JsonConfigConstants;
20 
21 import androidx.test.uiautomator.By;
22 import androidx.test.uiautomator.BySelector;
23 
24 import com.google.gson.annotations.SerializedName;
25 
26 import java.util.List;
27 import java.util.regex.Pattern;
28 
29 /**
30  * UI Resource For Spectatio Config JSON Config { "UI_ELEMENTS": { "CONFIG_NAME": { "TYPE":
31  * "RESOURCE_TYPE", "VALUE": "RESOURCE_VALUE", "PACKAGE": "RESOURCE_PACKAGE" } } }
32  *
33  * <p>RESOURCE_TYPE: TEXT, DESCRIPTION, RESOURCE_ID, TEXT_CONTAINS, CLASS; RESOURCE_VALUE: Value of
34  * the Resource; RESOURCE_PACKAGE: Package is required only to type RESOURCE_ID
35  *
36  * <p>Resource Value JSON { "TYPE": "RESOURCE_TYPE", "VALUE": "RESOURCE_VALUE", "PACKAGE":
37  * "RESOURCE_PACKAGE" } } } is referred in code using this class
38  */
39 public class UiElement {
40     // Type of UI Element - Resource ID, Text, Description, Class
41     @SerializedName("TYPE")
42     private String mType;
43 
44     @SerializedName("FLAG")
45     private boolean mFlag;
46 
47     @SerializedName("MAX_DEPTH")
48     private int mMaxDepth;
49 
50     // Value for the UI Resource - id, text value, description or class for the resource
51     @SerializedName("VALUE")
52     private String mValue;
53 
54     // Application Package for the UI Resource if the type is Resource ID,
55     @SerializedName("PACKAGE")
56     private String mPackage;
57 
58     // Each UiElementSpecifier that comprises a MULTIPLE specifier
59     @SerializedName("SPECIFIERS")
60     private List<UiElement> mSpecifiers;
61 
62     // The specifier for the parent of a HAS_ASCENDANT specifier
63     @SerializedName("ANCESTOR")
64     private UiElement mAncestor;
65 
66     // The specifier for the child of a HAS_DESCENDANT specifier
67     @SerializedName("DESCENDANT")
68     private UiElement mDescendant;
69 
UiElement(String type, boolean flag)70     public UiElement(String type, boolean flag) {
71         mType = type;
72         mFlag = flag;
73     }
74 
UiElement(String type, String value, String pkg)75     public UiElement(String type, String value, String pkg) {
76         mType = type;
77         mValue = value;
78         mPackage = pkg;
79     }
80 
UiElement(List<UiElement> specifiers)81     public UiElement(List<UiElement> specifiers) {
82         mType = JsonConfigConstants.MULTIPLE;
83         mSpecifiers = specifiers;
84     }
85 
UiElement(String type, UiElement relative, int maxDepth)86     public UiElement(String type, UiElement relative, int maxDepth) {
87         mType = type;
88         switch (type) {
89             case JsonConfigConstants.HAS_DESCENDANT:
90                 mDescendant = relative;
91                 break;
92             case JsonConfigConstants.HAS_ANCESTOR:
93                 mAncestor = relative;
94                 break;
95             default:
96                 throw new RuntimeException(
97                         "Unrecognized type given to UiElement constructor with relative argument");
98         }
99         mMaxDepth = maxDepth;
100     }
101 
102     /** Get Resource Type ( RESOURCE_ID, TEXT, DESCRIPTION, CLASS ) */
getType()103     public String getType() {
104         return mType;
105     }
106 
107     /** Get Resource Value ( resource id, text value, description, class ) */
getValue()108     public String getValue() {
109         return mValue;
110     }
111 
getPackage()112     public String getPackage() {
113         return mPackage;
114     }
115 
116     /** Convert a UI element from the config into a BySelector */
getBySelectorForUiElement()117     public BySelector getBySelectorForUiElement() {
118         switch (mType) {
119             case JsonConfigConstants.RESOURCE_ID:
120                 if (mPackage == null) {
121                     return By.res(Pattern.compile(".*" + Pattern.quote(":id/" + mValue)));
122                 }
123                 return By.res(mPackage, mValue);
124             case JsonConfigConstants.CLICKABLE:
125                 return By.clickable(mFlag);
126             case JsonConfigConstants.SCROLLABLE:
127                 return By.scrollable(mFlag);
128             case JsonConfigConstants.TEXT:
129                 return By.text(Pattern.compile(mValue, Pattern.CASE_INSENSITIVE));
130             case JsonConfigConstants.TEXT_CONTAINS:
131                 return By.textContains(mValue);
132             case JsonConfigConstants.DESCRIPTION:
133                 return By.desc(Pattern.compile(mValue, Pattern.CASE_INSENSITIVE));
134             case JsonConfigConstants.CLASS:
135                 if (mPackage != null && !mPackage.isEmpty()) {
136                     return By.clazz(mPackage, mValue);
137                 }
138                 return By.clazz(mValue);
139             case JsonConfigConstants.HAS_ANCESTOR:
140                 return By.hasAncestor(mAncestor.getBySelectorForUiElement(), mMaxDepth);
141             case JsonConfigConstants.HAS_DESCENDANT:
142                 return By.hasDescendant(mDescendant.getBySelectorForUiElement(), mMaxDepth);
143             case JsonConfigConstants.MULTIPLE:
144                 BySelector selector = null;
145                 for (UiElement specifier : mSpecifiers) {
146                     if (selector == null) {
147                         selector = specifier.getBySelectorForUiElement();
148                     } else {
149                         specifier.extendBySelectorForUiElement(selector);
150                     }
151                 }
152                 return selector;
153 
154             default:
155                 // Unknown UI Resource Type
156                 return null;
157         }
158     }
159 
extendBySelectorForUiElement(BySelector s)160     private void extendBySelectorForUiElement(BySelector s) {
161         switch (mType) {
162             case JsonConfigConstants.RESOURCE_ID:
163                 s.res(mPackage, mValue);
164                 break;
165             case JsonConfigConstants.CLICKABLE:
166                 s.clickable(mFlag);
167                 break;
168             case JsonConfigConstants.SCROLLABLE:
169                 s.scrollable(mFlag);
170                 break;
171             case JsonConfigConstants.TEXT:
172                 s.text(Pattern.compile(mValue, Pattern.CASE_INSENSITIVE));
173                 break;
174             case JsonConfigConstants.TEXT_CONTAINS:
175                 s.textContains(mValue);
176                 break;
177             case JsonConfigConstants.DESCRIPTION:
178                 s.desc(Pattern.compile(mValue, Pattern.CASE_INSENSITIVE));
179                 break;
180             case JsonConfigConstants.CLASS:
181                 if (mPackage != null && !mPackage.isEmpty()) {
182                     s.clazz(mPackage, mValue);
183                     return;
184                 }
185                 s.clazz(mValue);
186                 break;
187             case JsonConfigConstants.HAS_ANCESTOR:
188                 s.hasAncestor(mAncestor.getBySelectorForUiElement(), mMaxDepth);
189                 break;
190             case JsonConfigConstants.HAS_DESCENDANT:
191                 s.hasDescendant(mDescendant.getBySelectorForUiElement(), mMaxDepth);
192                 break;
193             case JsonConfigConstants.MULTIPLE:
194                 throw new UnsupportedOperationException(
195                         "You can't put a multiple-specifier inside a multiple-specifier.");
196             default:
197                 break;
198         }
199     }
200 }
201