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