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.ScrollActions; 20 import android.platform.spectatio.constants.JsonConfigConstants.ScrollDirection; 21 import android.platform.spectatio.constants.JsonConfigConstants.SupportedWorkFlowTasks; 22 import android.platform.spectatio.exceptions.MissingUiElementException; 23 import android.platform.spectatio.utils.SpectatioUiUtil; 24 import android.util.Log; 25 26 import androidx.test.uiautomator.BySelector; 27 import androidx.test.uiautomator.UiObject2; 28 29 import com.google.common.base.Strings; 30 import com.google.gson.annotations.SerializedName; 31 32 /** Workflow Task For Workflows in Spectatio Config JSON Config */ 33 public class WorkflowTask { 34 private static final String LOG_TAG = WorkflowTask.class.getSimpleName(); 35 36 @SerializedName("NAME") 37 private String mName; 38 39 @SerializedName("TYPE") 40 private String mType; 41 42 // TEXT or UI_ELEMENT based on the Type of Workflow Task 43 @SerializedName("CONFIG") 44 private WorkflowTaskConfig mTaskConfig; 45 46 // Number of times to repeat given task, default is 0 47 // By default, task will be execute once and won't be repeated as repeat count is 0 48 @SerializedName("REPEAT_COUNT") 49 private int mRepeatCount; 50 51 // If task needs scrolling, provide Scroll Config 52 @SerializedName("SCROLL_CONFIG") 53 private ScrollConfig mScrollConfig; 54 WorkflowTask( String name, String type, WorkflowTaskConfig taskConfig, int repeatCount, ScrollConfig scrollConfig)55 public WorkflowTask( 56 String name, 57 String type, 58 WorkflowTaskConfig taskConfig, 59 int repeatCount, 60 ScrollConfig scrollConfig) { 61 mName = name; 62 mType = type; 63 mTaskConfig = taskConfig; 64 mRepeatCount = repeatCount; 65 mScrollConfig = scrollConfig; 66 } 67 getTaskName()68 public String getTaskName() { 69 return mName; 70 } 71 getTaskType()72 public String getTaskType() { 73 return mType; 74 } 75 getTaskConfig()76 public WorkflowTaskConfig getTaskConfig() { 77 return mTaskConfig; 78 } 79 getRepeatCount()80 public int getRepeatCount() { 81 return mRepeatCount; 82 } 83 getScrollConfig()84 public ScrollConfig getScrollConfig() { 85 return mScrollConfig; 86 } 87 executeTask(String workflowName, SpectatioUiUtil spectatioUiUtil)88 public void executeTask(String workflowName, SpectatioUiUtil spectatioUiUtil) { 89 Log.i( 90 LOG_TAG, 91 String.format( 92 "Executing Task %s with Type %s for Workflow %s", 93 mName, mType, workflowName)); 94 95 SupportedWorkFlowTasks taskType = null; 96 try { 97 taskType = SupportedWorkFlowTasks.valueOf(mType); 98 } catch (IllegalArgumentException ex) { 99 throwRuntimeException("Workflow Task Type", mType, workflowName, "Not Supported"); 100 } 101 102 int executionCount = 0; 103 // Execute Task once by default. Repeat it again based on repeat count i.e. mRepeatCount > 0 104 do { 105 executeTask(taskType, workflowName, spectatioUiUtil); 106 executionCount++; 107 Log.i( 108 LOG_TAG, 109 String.format( 110 "Completed executing Task %s, %d time(s).", mName, executionCount)); 111 112 // Wait for 1 Second before executing another task 113 spectatioUiUtil.wait1Second(); 114 } while (executionCount < (1 + mRepeatCount)); 115 116 Log.i( 117 LOG_TAG, 118 String.format( 119 "Done Executing Task %s with Type %s for Workflow %s", 120 mName, mType, workflowName)); 121 } 122 executeTask( SupportedWorkFlowTasks taskType, String workflowName, SpectatioUiUtil spectatioUiUtil)123 private void executeTask( 124 SupportedWorkFlowTasks taskType, String workflowName, SpectatioUiUtil spectatioUiUtil) { 125 switch (taskType) { 126 case COMMAND: 127 validateAndExecuteCommand(workflowName, spectatioUiUtil); 128 break; 129 case HAS_PACKAGE_IN_FOREGROUND: 130 validateAndVerifyPackage(workflowName, spectatioUiUtil); 131 break; 132 case HAS_UI_ELEMENT_IN_FOREGROUND: 133 validateAndVerifyUiElement(workflowName, spectatioUiUtil); 134 break; 135 case CLICK: 136 validateAndClickUiElement( 137 workflowName, 138 spectatioUiUtil, 139 /* scrollToFind= */ false, 140 /* isLongClick= */ false, 141 /* isOptional= */ false); 142 break; 143 case CLICK_IF_EXIST: 144 validateAndClickUiElement( 145 workflowName, 146 spectatioUiUtil, 147 /* scrollToFind= */ false, 148 /* isLongClick= */ false, 149 /* isOptional= */ true); 150 break; 151 case LONG_CLICK: 152 validateAndClickUiElement( 153 workflowName, 154 spectatioUiUtil, 155 /* scrollToFind= */ false, 156 /* isLongClick= */ true, 157 /* isOptional= */ false); 158 break; 159 case PRESS: 160 validateAndPressKey(workflowName, spectatioUiUtil, /* isLongPress= */ false); 161 break; 162 case LONG_PRESS: 163 validateAndPressKey(workflowName, spectatioUiUtil, /* isLongPress= */ true); 164 break; 165 case SCROLL_TO_FIND_AND_CLICK: 166 validateAndClickUiElement( 167 workflowName, 168 spectatioUiUtil, 169 /* scrollToFind= */ true, 170 /* isLongClick= */ false, 171 /* isOptional= */ false); 172 break; 173 case SCROLL_TO_FIND_AND_CLICK_IF_EXIST: 174 validateAndClickUiElement( 175 workflowName, 176 spectatioUiUtil, 177 /* scrollToFind= */ true, 178 /* isLongClick= */ false, 179 /* isOptional= */ true); 180 break; 181 case WAIT_MS: 182 validateAndWait(workflowName, spectatioUiUtil); 183 break; 184 default: 185 throwRuntimeException("Workflow Task Type", mType, workflowName, "Not Supported"); 186 } 187 } 188 validateAndWait(String workflowName, SpectatioUiUtil spectatioUiUtil)189 private void validateAndWait(String workflowName, SpectatioUiUtil spectatioUiUtil) { 190 String waitTime = validateAndGetTaskConfigText(workflowName); 191 if (!isValidInteger(/* action= */ "WAIT_MS Value", waitTime, workflowName)) { 192 throwRuntimeException("Wait", waitTime, workflowName, "Invalid"); 193 } 194 spectatioUiUtil.waitNSeconds(Integer.parseInt(waitTime)); 195 } 196 validateAndExecuteCommand(String workflowName, SpectatioUiUtil spectatioUiUtil)197 private void validateAndExecuteCommand(String workflowName, SpectatioUiUtil spectatioUiUtil) { 198 String command = validateAndGetTaskConfigText(workflowName); 199 spectatioUiUtil.executeShellCommand(command); 200 } 201 validateAndVerifyUiElement(String workflowName, SpectatioUiUtil spectatioUiUtil)202 private void validateAndVerifyUiElement(String workflowName, SpectatioUiUtil spectatioUiUtil) { 203 UiElement uiElement = validateAndGetTaskConfigUiElement(workflowName); 204 BySelector selector = uiElement.getBySelectorForUiElement(); 205 if (!spectatioUiUtil.hasUiElement(selector)) { 206 throwRuntimeException( 207 "UI Element", selector.toString(), workflowName, "Not in Foreground"); 208 } 209 } 210 validateAndVerifyPackage(String workflowName, SpectatioUiUtil spectatioUiUtil)211 private void validateAndVerifyPackage(String workflowName, SpectatioUiUtil spectatioUiUtil) { 212 String pkg = validateAndGetTaskConfigText(workflowName); 213 if (!spectatioUiUtil.hasPackageInForeground(pkg)) { 214 throwRuntimeException("Package", pkg, workflowName, "Not in Foreground"); 215 } 216 } 217 validateAndClickUiElement( String workflowName, SpectatioUiUtil spectatioUiUtil, boolean scrollToFind, boolean isLongClick, boolean isOptional)218 private void validateAndClickUiElement( 219 String workflowName, 220 SpectatioUiUtil spectatioUiUtil, 221 boolean scrollToFind, 222 boolean isLongClick, 223 boolean isOptional) { 224 UiElement uiElement = validateAndGetTaskConfigUiElement(workflowName); 225 BySelector selector = uiElement.getBySelectorForUiElement(); 226 UiObject2 uiObject = spectatioUiUtil.findUiObject(selector); 227 if (scrollToFind && !isValidUiObject(uiObject)) { 228 ScrollConfig scrollConfig = validateAndGetTaskScrollConfig(workflowName); 229 ScrollActions scrollAction = null; 230 try { 231 scrollAction = ScrollActions.valueOf(scrollConfig.getScrollAction()); 232 } catch (IllegalArgumentException ex) { 233 throwRuntimeException( 234 "Scroll Action", 235 scrollConfig.getScrollAction(), 236 workflowName, 237 "Not Supported"); 238 } 239 try { 240 switch (scrollAction) { 241 case USE_BUTTON: 242 BySelector forwardButtonSelector = 243 scrollConfig.getScrollForwardButton().getBySelectorForUiElement(); 244 BySelector backwardButtonSelector = 245 scrollConfig.getScrollBackwardButton().getBySelectorForUiElement(); 246 uiObject = 247 spectatioUiUtil.scrollAndFindUiObject( 248 forwardButtonSelector, backwardButtonSelector, selector); 249 break; 250 case USE_GESTURE: 251 BySelector scrollElementSelector = 252 scrollConfig.getScrollElement().getBySelectorForUiElement(); 253 Integer scrollMargin = Integer.valueOf(scrollConfig.getScrollMargin()); 254 Integer scrollWaitTime = Integer.valueOf(scrollConfig.getScrollWaitTime()); 255 256 ScrollDirection scrollDirection = null; 257 try { 258 scrollDirection = 259 ScrollDirection.valueOf(scrollConfig.getScrollDirection()); 260 } catch (IllegalArgumentException ex) { 261 throwRuntimeException( 262 "Scroll Direction", 263 scrollConfig.getScrollDirection(), 264 workflowName, 265 "Not Supported"); 266 } 267 spectatioUiUtil.addScrollValues(scrollMargin, scrollWaitTime); 268 uiObject = 269 spectatioUiUtil.scrollAndFindUiObject( 270 scrollElementSelector, 271 selector, 272 (scrollDirection == ScrollDirection.VERTICAL)); 273 break; 274 default: 275 throwRuntimeException( 276 "Scroll Action", 277 scrollConfig.getScrollAction(), 278 workflowName, 279 "Not Supported"); 280 } 281 } catch (MissingUiElementException ex) { 282 throwRuntimeException( 283 "Scroll Button or Element for Scroll Action", 284 scrollConfig.getScrollAction(), 285 workflowName, 286 String.format("Missing. Error: %s", ex.getMessage())); 287 } 288 } 289 if (isOptional && !isValidUiObject(uiObject)) { 290 return; 291 } 292 validateUiObject(uiObject, workflowName); 293 if (isLongClick) { 294 spectatioUiUtil.longPress(uiObject); 295 } else { 296 spectatioUiUtil.clickAndWait(uiObject); 297 } 298 } 299 validateAndPressKey( String workflowName, SpectatioUiUtil spectatioUiUtil, boolean isLongPress)300 private void validateAndPressKey( 301 String workflowName, SpectatioUiUtil spectatioUiUtil, boolean isLongPress) { 302 String key = validateAndGetTaskConfigText(workflowName); 303 // Check if Key is an integer i.e. KeyCode 304 if (isValidInteger(/* action= */ "PRESS", key, workflowName)) { 305 int keyCode = Integer.parseInt(key); 306 if (isLongPress) { 307 spectatioUiUtil.longPressKey(keyCode); 308 } else { 309 spectatioUiUtil.pressKeyCode(keyCode); 310 } 311 return; 312 } 313 switch (key) { 314 case "POWER": 315 if (isLongPress) { 316 spectatioUiUtil.longPressPower(); 317 } else { 318 spectatioUiUtil.pressPower(); 319 } 320 break; 321 case "HOME": 322 if (isLongPress) { 323 throwRuntimeException("Long Press", key, workflowName, "Not Supported"); 324 } else { 325 spectatioUiUtil.pressHome(); 326 } 327 break; 328 case "BACK": 329 if (isLongPress) { 330 throwRuntimeException("Long Press", key, workflowName, "Not Supported"); 331 } else { 332 spectatioUiUtil.pressBack(); 333 } 334 break; 335 case "SCREEN_CENTER": 336 if (isLongPress) { 337 spectatioUiUtil.longPressScreenCenter(); 338 } else { 339 throwRuntimeException("Press", key, workflowName, "Not Supported"); 340 } 341 break; 342 case "WAKE_UP": 343 if (isLongPress) { 344 throwRuntimeException("Long Press", key, workflowName, "Not Supported"); 345 } else { 346 spectatioUiUtil.wakeUp(); 347 } 348 break; 349 default: 350 throwRuntimeException("Config", key, workflowName, "Not Supported"); 351 } 352 } 353 isValidInteger(String action, String value, String workflowName)354 private boolean isValidInteger(String action, String value, String workflowName) { 355 try { 356 int intValue = Integer.parseInt(value); 357 if (intValue < 0) { 358 throwRuntimeException(action, value, workflowName, "Invalid"); 359 } 360 } catch (NumberFormatException ex) { 361 return false; 362 } 363 return true; 364 } 365 isValidUiObject(UiObject2 uiObject)366 private boolean isValidUiObject(UiObject2 uiObject) { 367 return uiObject != null; 368 } 369 validateUiObject(UiObject2 uiObject, String workflowName)370 private void validateUiObject(UiObject2 uiObject, String workflowName) { 371 if (!isValidUiObject(uiObject)) { 372 throwRuntimeException( 373 "UI Element for Config", "UI_ELEMENT", workflowName, "Missing on Device UI"); 374 } 375 } 376 validateAndGetTaskConfigText(String workflowName)377 private String validateAndGetTaskConfigText(String workflowName) { 378 String taskConfigText = mTaskConfig.getText(); 379 if (Strings.isNullOrEmpty(taskConfigText)) { 380 throwRuntimeException( 381 "Config Text", taskConfigText, workflowName, "Missing or Invalid"); 382 } 383 return taskConfigText.trim(); 384 } 385 validateAndGetTaskConfigUiElement(String workflowName)386 private UiElement validateAndGetTaskConfigUiElement(String workflowName) { 387 UiElement uiElement = mTaskConfig.getUiElement(); 388 if (uiElement == null) { 389 throwRuntimeException("Config", "UI_ELEMENT", workflowName, "Missing or Invalid"); 390 } 391 return uiElement; 392 } 393 validateAndGetTaskScrollConfig(String workflowName)394 private ScrollConfig validateAndGetTaskScrollConfig(String workflowName) { 395 if (mScrollConfig == null) { 396 throwRuntimeException("Config", "SCROLL_CONFIG", workflowName, "Missing or Invalid"); 397 } 398 return mScrollConfig; 399 } 400 throwRuntimeException( String property, String value, String workflowName, String reason)401 private void throwRuntimeException( 402 String property, String value, String workflowName, String reason) { 403 throw new RuntimeException( 404 String.format( 405 "%s %s for task %s with type %s in Workflow %s is %s.", 406 property, value, mName, mType, workflowName, reason)); 407 } 408 } 409