1 /* 2 * Copyright (C) 2012 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 com.android.uiautomator.core; 18 19 import android.util.SparseArray; 20 import android.view.accessibility.AccessibilityNodeInfo; 21 22 import java.util.regex.Pattern; 23 24 /** 25 * Specifies the elements in the layout hierarchy for tests to target, filtered 26 * by properties such as text value, content-description, class name, and state 27 * information. You can also target an element by its location in a layout 28 * hierarchy. 29 * @since API Level 16 30 */ 31 public class UiSelector { 32 static final int SELECTOR_NIL = 0; 33 static final int SELECTOR_TEXT = 1; 34 static final int SELECTOR_START_TEXT = 2; 35 static final int SELECTOR_CONTAINS_TEXT = 3; 36 static final int SELECTOR_CLASS = 4; 37 static final int SELECTOR_DESCRIPTION = 5; 38 static final int SELECTOR_START_DESCRIPTION = 6; 39 static final int SELECTOR_CONTAINS_DESCRIPTION = 7; 40 static final int SELECTOR_INDEX = 8; 41 static final int SELECTOR_INSTANCE = 9; 42 static final int SELECTOR_ENABLED = 10; 43 static final int SELECTOR_FOCUSED = 11; 44 static final int SELECTOR_FOCUSABLE = 12; 45 static final int SELECTOR_SCROLLABLE = 13; 46 static final int SELECTOR_CLICKABLE = 14; 47 static final int SELECTOR_CHECKED = 15; 48 static final int SELECTOR_SELECTED = 16; 49 static final int SELECTOR_ID = 17; 50 static final int SELECTOR_PACKAGE_NAME = 18; 51 static final int SELECTOR_CHILD = 19; 52 static final int SELECTOR_CONTAINER = 20; 53 static final int SELECTOR_PATTERN = 21; 54 static final int SELECTOR_PARENT = 22; 55 static final int SELECTOR_COUNT = 23; 56 static final int SELECTOR_LONG_CLICKABLE = 24; 57 static final int SELECTOR_TEXT_REGEX = 25; 58 static final int SELECTOR_CLASS_REGEX = 26; 59 static final int SELECTOR_DESCRIPTION_REGEX = 27; 60 static final int SELECTOR_PACKAGE_NAME_REGEX = 28; 61 static final int SELECTOR_RESOURCE_ID = 29; 62 static final int SELECTOR_CHECKABLE = 30; 63 static final int SELECTOR_RESOURCE_ID_REGEX = 31; 64 65 private SparseArray<Object> mSelectorAttributes = new SparseArray<Object>(); 66 67 /** 68 * @since API Level 16 69 */ UiSelector()70 public UiSelector() { 71 } 72 UiSelector(UiSelector selector)73 UiSelector(UiSelector selector) { 74 mSelectorAttributes = selector.cloneSelector().mSelectorAttributes; 75 } 76 77 /** 78 * @since API Level 17 79 */ cloneSelector()80 protected UiSelector cloneSelector() { 81 UiSelector ret = new UiSelector(); 82 ret.mSelectorAttributes = mSelectorAttributes.clone(); 83 if (hasChildSelector()) 84 ret.mSelectorAttributes.put(SELECTOR_CHILD, new UiSelector(getChildSelector())); 85 if (hasParentSelector()) 86 ret.mSelectorAttributes.put(SELECTOR_PARENT, new UiSelector(getParentSelector())); 87 if (hasPatternSelector()) 88 ret.mSelectorAttributes.put(SELECTOR_PATTERN, new UiSelector(getPatternSelector())); 89 return ret; 90 } 91 patternBuilder(UiSelector selector)92 static UiSelector patternBuilder(UiSelector selector) { 93 if (!selector.hasPatternSelector()) { 94 return new UiSelector().patternSelector(selector); 95 } 96 return selector; 97 } 98 patternBuilder(UiSelector container, UiSelector pattern)99 static UiSelector patternBuilder(UiSelector container, UiSelector pattern) { 100 return new UiSelector( 101 new UiSelector().containerSelector(container).patternSelector(pattern)); 102 } 103 104 /** 105 * Set the search criteria to match the visible text displayed 106 * in a widget (for example, the text label to launch an app). 107 * 108 * The text for the element must match exactly with the string in your input 109 * argument. Matching is case-sensitive. 110 * 111 * @param text Value to match 112 * @return UiSelector with the specified search criteria 113 * @since API Level 16 114 */ text(String text)115 public UiSelector text(String text) { 116 return buildSelector(SELECTOR_TEXT, text); 117 } 118 119 /** 120 * Set the search criteria to match the visible text displayed in a layout 121 * element, using a regular expression. 122 * 123 * The text in the widget must match exactly with the string in your 124 * input argument. 125 * 126 * @param regex a regular expression 127 * @return UiSelector with the specified search criteria 128 * @since API Level 17 129 */ textMatches(String regex)130 public UiSelector textMatches(String regex) { 131 return buildSelector(SELECTOR_TEXT_REGEX, Pattern.compile(regex)); 132 } 133 134 /** 135 * Set the search criteria to match visible text in a widget that is 136 * prefixed by the text parameter. 137 * 138 * The matching is case-insensitive. 139 * 140 * @param text Value to match 141 * @return UiSelector with the specified search criteria 142 * @since API Level 16 143 */ textStartsWith(String text)144 public UiSelector textStartsWith(String text) { 145 return buildSelector(SELECTOR_START_TEXT, text); 146 } 147 148 /** 149 * Set the search criteria to match the visible text in a widget 150 * where the visible text must contain the string in your input argument. 151 * 152 * The matching is case-sensitive. 153 * 154 * @param text Value to match 155 * @return UiSelector with the specified search criteria 156 * @since API Level 16 157 */ textContains(String text)158 public UiSelector textContains(String text) { 159 return buildSelector(SELECTOR_CONTAINS_TEXT, text); 160 } 161 162 /** 163 * Set the search criteria to match the class property 164 * for a widget (for example, "android.widget.Button"). 165 * 166 * @param className Value to match 167 * @return UiSelector with the specified search criteria 168 * @since API Level 16 169 */ className(String className)170 public UiSelector className(String className) { 171 return buildSelector(SELECTOR_CLASS, className); 172 } 173 174 /** 175 * Set the search criteria to match the class property 176 * for a widget, using a regular expression. 177 * 178 * @param regex a regular expression 179 * @return UiSelector with the specified search criteria 180 * @since API Level 17 181 */ classNameMatches(String regex)182 public UiSelector classNameMatches(String regex) { 183 return buildSelector(SELECTOR_CLASS_REGEX, Pattern.compile(regex)); 184 } 185 186 /** 187 * Set the search criteria to match the class property 188 * for a widget (for example, "android.widget.Button"). 189 * 190 * @param type type 191 * @return UiSelector with the specified search criteria 192 * @since API Level 17 193 */ className(Class<T> type)194 public <T> UiSelector className(Class<T> type) { 195 return buildSelector(SELECTOR_CLASS, type.getName()); 196 } 197 198 /** 199 * Set the search criteria to match the content-description 200 * property for a widget. 201 * 202 * The content-description is typically used 203 * by the Android Accessibility framework to 204 * provide an audio prompt for the widget when 205 * the widget is selected. The content-description 206 * for the widget must match exactly 207 * with the string in your input argument. 208 * 209 * Matching is case-sensitive. 210 * 211 * @param desc Value to match 212 * @return UiSelector with the specified search criteria 213 * @since API Level 16 214 */ description(String desc)215 public UiSelector description(String desc) { 216 return buildSelector(SELECTOR_DESCRIPTION, desc); 217 } 218 219 /** 220 * Set the search criteria to match the content-description 221 * property for a widget. 222 * 223 * The content-description is typically used 224 * by the Android Accessibility framework to 225 * provide an audio prompt for the widget when 226 * the widget is selected. The content-description 227 * for the widget must match exactly 228 * with the string in your input argument. 229 * 230 * @param regex a regular expression 231 * @return UiSelector with the specified search criteria 232 * @since API Level 17 233 */ descriptionMatches(String regex)234 public UiSelector descriptionMatches(String regex) { 235 return buildSelector(SELECTOR_DESCRIPTION_REGEX, Pattern.compile(regex)); 236 } 237 238 /** 239 * Set the search criteria to match the content-description 240 * property for a widget. 241 * 242 * The content-description is typically used 243 * by the Android Accessibility framework to 244 * provide an audio prompt for the widget when 245 * the widget is selected. The content-description 246 * for the widget must start 247 * with the string in your input argument. 248 * 249 * Matching is case-insensitive. 250 * 251 * @param desc Value to match 252 * @return UiSelector with the specified search criteria 253 * @since API Level 16 254 */ descriptionStartsWith(String desc)255 public UiSelector descriptionStartsWith(String desc) { 256 return buildSelector(SELECTOR_START_DESCRIPTION, desc); 257 } 258 259 /** 260 * Set the search criteria to match the content-description 261 * property for a widget. 262 * 263 * The content-description is typically used 264 * by the Android Accessibility framework to 265 * provide an audio prompt for the widget when 266 * the widget is selected. The content-description 267 * for the widget must contain 268 * the string in your input argument. 269 * 270 * Matching is case-insensitive. 271 * 272 * @param desc Value to match 273 * @return UiSelector with the specified search criteria 274 * @since API Level 16 275 */ descriptionContains(String desc)276 public UiSelector descriptionContains(String desc) { 277 return buildSelector(SELECTOR_CONTAINS_DESCRIPTION, desc); 278 } 279 280 /** 281 * Set the search criteria to match the given resource ID. 282 * 283 * @param id Value to match 284 * @return UiSelector with the specified search criteria 285 * @since API Level 18 286 */ resourceId(String id)287 public UiSelector resourceId(String id) { 288 return buildSelector(SELECTOR_RESOURCE_ID, id); 289 } 290 291 /** 292 * Set the search criteria to match the resource ID 293 * of the widget, using a regular expression. 294 * 295 * @param regex a regular expression 296 * @return UiSelector with the specified search criteria 297 * @since API Level 18 298 */ resourceIdMatches(String regex)299 public UiSelector resourceIdMatches(String regex) { 300 return buildSelector(SELECTOR_RESOURCE_ID_REGEX, Pattern.compile(regex)); 301 } 302 303 /** 304 * Set the search criteria to match the widget by its node 305 * index in the layout hierarchy. 306 * 307 * The index value must be 0 or greater. 308 * 309 * Using the index can be unreliable and should only 310 * be used as a last resort for matching. Instead, 311 * consider using the {@link #instance(int)} method. 312 * 313 * @param index Value to match 314 * @return UiSelector with the specified search criteria 315 * @since API Level 16 316 */ index(final int index)317 public UiSelector index(final int index) { 318 return buildSelector(SELECTOR_INDEX, index); 319 } 320 321 /** 322 * Set the search criteria to match the 323 * widget by its instance number. 324 * 325 * The instance value must be 0 or greater, where 326 * the first instance is 0. 327 * 328 * For example, to simulate a user click on 329 * the third image that is enabled in a UI screen, you 330 * could specify a a search criteria where the instance is 331 * 2, the {@link #className(String)} matches the image 332 * widget class, and {@link #enabled(boolean)} is true. 333 * The code would look like this: 334 * <code> 335 * new UiSelector().className("android.widget.ImageView") 336 * .enabled(true).instance(2); 337 * </code> 338 * 339 * @param instance Value to match 340 * @return UiSelector with the specified search criteria 341 * @since API Level 16 342 */ instance(final int instance)343 public UiSelector instance(final int instance) { 344 return buildSelector(SELECTOR_INSTANCE, instance); 345 } 346 347 /** 348 * Set the search criteria to match widgets that are enabled. 349 * 350 * Typically, using this search criteria alone is not useful. 351 * You should also include additional criteria, such as text, 352 * content-description, or the class name for a widget. 353 * 354 * If no other search criteria is specified, and there is more 355 * than one matching widget, the first widget in the tree 356 * is selected. 357 * 358 * @param val Value to match 359 * @return UiSelector with the specified search criteria 360 * @since API Level 16 361 */ enabled(boolean val)362 public UiSelector enabled(boolean val) { 363 return buildSelector(SELECTOR_ENABLED, val); 364 } 365 366 /** 367 * Set the search criteria to match widgets that have focus. 368 * 369 * Typically, using this search criteria alone is not useful. 370 * You should also include additional criteria, such as text, 371 * content-description, or the class name for a widget. 372 * 373 * If no other search criteria is specified, and there is more 374 * than one matching widget, the first widget in the tree 375 * is selected. 376 * 377 * @param val Value to match 378 * @return UiSelector with the specified search criteria 379 * @since API Level 16 380 */ focused(boolean val)381 public UiSelector focused(boolean val) { 382 return buildSelector(SELECTOR_FOCUSED, val); 383 } 384 385 /** 386 * Set the search criteria to match widgets that are focusable. 387 * 388 * Typically, using this search criteria alone is not useful. 389 * You should also include additional criteria, such as text, 390 * content-description, or the class name for a widget. 391 * 392 * If no other search criteria is specified, and there is more 393 * than one matching widget, the first widget in the tree 394 * is selected. 395 * 396 * @param val Value to match 397 * @return UiSelector with the specified search criteria 398 * @since API Level 16 399 */ focusable(boolean val)400 public UiSelector focusable(boolean val) { 401 return buildSelector(SELECTOR_FOCUSABLE, val); 402 } 403 404 /** 405 * Set the search criteria to match widgets that are scrollable. 406 * 407 * Typically, using this search criteria alone is not useful. 408 * You should also include additional criteria, such as text, 409 * content-description, or the class name for a widget. 410 * 411 * If no other search criteria is specified, and there is more 412 * than one matching widget, the first widget in the tree 413 * is selected. 414 * 415 * @param val Value to match 416 * @return UiSelector with the specified search criteria 417 * @since API Level 16 418 */ scrollable(boolean val)419 public UiSelector scrollable(boolean val) { 420 return buildSelector(SELECTOR_SCROLLABLE, val); 421 } 422 423 /** 424 * Set the search criteria to match widgets that 425 * are currently selected. 426 * 427 * Typically, using this search criteria alone is not useful. 428 * You should also include additional criteria, such as text, 429 * content-description, or the class name for a widget. 430 * 431 * If no other search criteria is specified, and there is more 432 * than one matching widget, the first widget in the tree 433 * is selected. 434 * 435 * @param val Value to match 436 * @return UiSelector with the specified search criteria 437 * @since API Level 16 438 */ selected(boolean val)439 public UiSelector selected(boolean val) { 440 return buildSelector(SELECTOR_SELECTED, val); 441 } 442 443 /** 444 * Set the search criteria to match widgets that 445 * are currently checked (usually for checkboxes). 446 * 447 * Typically, using this search criteria alone is not useful. 448 * You should also include additional criteria, such as text, 449 * content-description, or the class name for a widget. 450 * 451 * If no other search criteria is specified, and there is more 452 * than one matching widget, the first widget in the tree 453 * is selected. 454 * 455 * @param val Value to match 456 * @return UiSelector with the specified search criteria 457 * @since API Level 16 458 */ checked(boolean val)459 public UiSelector checked(boolean val) { 460 return buildSelector(SELECTOR_CHECKED, val); 461 } 462 463 /** 464 * Set the search criteria to match widgets that are clickable. 465 * 466 * Typically, using this search criteria alone is not useful. 467 * You should also include additional criteria, such as text, 468 * content-description, or the class name for a widget. 469 * 470 * If no other search criteria is specified, and there is more 471 * than one matching widget, the first widget in the tree 472 * is selected. 473 * 474 * @param val Value to match 475 * @return UiSelector with the specified search criteria 476 * @since API Level 16 477 */ clickable(boolean val)478 public UiSelector clickable(boolean val) { 479 return buildSelector(SELECTOR_CLICKABLE, val); 480 } 481 482 /** 483 * Set the search criteria to match widgets that are checkable. 484 * 485 * Typically, using this search criteria alone is not useful. 486 * You should also include additional criteria, such as text, 487 * content-description, or the class name for a widget. 488 * 489 * If no other search criteria is specified, and there is more 490 * than one matching widget, the first widget in the tree 491 * is selected. 492 * 493 * @param val Value to match 494 * @return UiSelector with the specified search criteria 495 * @since API Level 18 496 */ checkable(boolean val)497 public UiSelector checkable(boolean val) { 498 return buildSelector(SELECTOR_CHECKABLE, val); 499 } 500 501 /** 502 * Set the search criteria to match widgets that are long-clickable. 503 * 504 * Typically, using this search criteria alone is not useful. 505 * You should also include additional criteria, such as text, 506 * content-description, or the class name for a widget. 507 * 508 * If no other search criteria is specified, and there is more 509 * than one matching widget, the first widget in the tree 510 * is selected. 511 * 512 * @param val Value to match 513 * @return UiSelector with the specified search criteria 514 * @since API Level 17 515 */ longClickable(boolean val)516 public UiSelector longClickable(boolean val) { 517 return buildSelector(SELECTOR_LONG_CLICKABLE, val); 518 } 519 520 /** 521 * Adds a child UiSelector criteria to this selector. 522 * 523 * Use this selector to narrow the search scope to 524 * child widgets under a specific parent widget. 525 * 526 * @param selector 527 * @return UiSelector with this added search criterion 528 * @since API Level 16 529 */ childSelector(UiSelector selector)530 public UiSelector childSelector(UiSelector selector) { 531 return buildSelector(SELECTOR_CHILD, selector); 532 } 533 patternSelector(UiSelector selector)534 private UiSelector patternSelector(UiSelector selector) { 535 return buildSelector(SELECTOR_PATTERN, selector); 536 } 537 containerSelector(UiSelector selector)538 private UiSelector containerSelector(UiSelector selector) { 539 return buildSelector(SELECTOR_CONTAINER, selector); 540 } 541 542 /** 543 * Adds a child UiSelector criteria to this selector which is used to 544 * start search from the parent widget. 545 * 546 * Use this selector to narrow the search scope to 547 * sibling widgets as well all child widgets under a parent. 548 * 549 * @param selector 550 * @return UiSelector with this added search criterion 551 * @since API Level 16 552 */ fromParent(UiSelector selector)553 public UiSelector fromParent(UiSelector selector) { 554 return buildSelector(SELECTOR_PARENT, selector); 555 } 556 557 /** 558 * Set the search criteria to match the package name 559 * of the application that contains the widget. 560 * 561 * @param name Value to match 562 * @return UiSelector with the specified search criteria 563 * @since API Level 16 564 */ packageName(String name)565 public UiSelector packageName(String name) { 566 return buildSelector(SELECTOR_PACKAGE_NAME, name); 567 } 568 569 /** 570 * Set the search criteria to match the package name 571 * of the application that contains the widget. 572 * 573 * @param regex a regular expression 574 * @return UiSelector with the specified search criteria 575 * @since API Level 17 576 */ packageNameMatches(String regex)577 public UiSelector packageNameMatches(String regex) { 578 return buildSelector(SELECTOR_PACKAGE_NAME_REGEX, Pattern.compile(regex)); 579 } 580 581 /** 582 * Building a UiSelector always returns a new UiSelector and never modifies the 583 * existing UiSelector being used. 584 */ buildSelector(int selectorId, Object selectorValue)585 private UiSelector buildSelector(int selectorId, Object selectorValue) { 586 UiSelector selector = new UiSelector(this); 587 if (selectorId == SELECTOR_CHILD || selectorId == SELECTOR_PARENT) 588 selector.getLastSubSelector().mSelectorAttributes.put(selectorId, selectorValue); 589 else 590 selector.mSelectorAttributes.put(selectorId, selectorValue); 591 return selector; 592 } 593 594 /** 595 * Selectors may have a hierarchy defined by specifying child nodes to be matched. 596 * It is not necessary that every selector have more than one level. A selector 597 * can also be a single level referencing only one node. In such cases the return 598 * it null. 599 * 600 * @return a child selector if one exists. Else null if this selector does not 601 * reference child node. 602 */ getChildSelector()603 UiSelector getChildSelector() { 604 UiSelector selector = (UiSelector)mSelectorAttributes.get(UiSelector.SELECTOR_CHILD, null); 605 if (selector != null) 606 return new UiSelector(selector); 607 return null; 608 } 609 getPatternSelector()610 UiSelector getPatternSelector() { 611 UiSelector selector = 612 (UiSelector)mSelectorAttributes.get(UiSelector.SELECTOR_PATTERN, null); 613 if (selector != null) 614 return new UiSelector(selector); 615 return null; 616 } 617 getContainerSelector()618 UiSelector getContainerSelector() { 619 UiSelector selector = 620 (UiSelector)mSelectorAttributes.get(UiSelector.SELECTOR_CONTAINER, null); 621 if (selector != null) 622 return new UiSelector(selector); 623 return null; 624 } 625 getParentSelector()626 UiSelector getParentSelector() { 627 UiSelector selector = 628 (UiSelector) mSelectorAttributes.get(UiSelector.SELECTOR_PARENT, null); 629 if (selector != null) 630 return new UiSelector(selector); 631 return null; 632 } 633 getInstance()634 int getInstance() { 635 return getInt(UiSelector.SELECTOR_INSTANCE); 636 } 637 getString(int criterion)638 String getString(int criterion) { 639 return (String) mSelectorAttributes.get(criterion, null); 640 } 641 getBoolean(int criterion)642 boolean getBoolean(int criterion) { 643 return (Boolean) mSelectorAttributes.get(criterion, false); 644 } 645 getInt(int criterion)646 int getInt(int criterion) { 647 return (Integer) mSelectorAttributes.get(criterion, 0); 648 } 649 getPattern(int criterion)650 Pattern getPattern(int criterion) { 651 return (Pattern) mSelectorAttributes.get(criterion, null); 652 } 653 isMatchFor(AccessibilityNodeInfo node, int index)654 boolean isMatchFor(AccessibilityNodeInfo node, int index) { 655 int size = mSelectorAttributes.size(); 656 for(int x = 0; x < size; x++) { 657 CharSequence s = null; 658 int criterion = mSelectorAttributes.keyAt(x); 659 switch(criterion) { 660 case UiSelector.SELECTOR_INDEX: 661 if (index != this.getInt(criterion)) 662 return false; 663 break; 664 case UiSelector.SELECTOR_CHECKED: 665 if (node.isChecked() != getBoolean(criterion)) { 666 return false; 667 } 668 break; 669 case UiSelector.SELECTOR_CLASS: 670 s = node.getClassName(); 671 if (s == null || !s.toString().contentEquals(getString(criterion))) { 672 return false; 673 } 674 break; 675 case UiSelector.SELECTOR_CLASS_REGEX: 676 s = node.getClassName(); 677 if (s == null || !getPattern(criterion).matcher(s).matches()) { 678 return false; 679 } 680 break; 681 case UiSelector.SELECTOR_CLICKABLE: 682 if (node.isClickable() != getBoolean(criterion)) { 683 return false; 684 } 685 break; 686 case UiSelector.SELECTOR_CHECKABLE: 687 if (node.isCheckable() != getBoolean(criterion)) { 688 return false; 689 } 690 break; 691 case UiSelector.SELECTOR_LONG_CLICKABLE: 692 if (node.isLongClickable() != getBoolean(criterion)) { 693 return false; 694 } 695 break; 696 case UiSelector.SELECTOR_CONTAINS_DESCRIPTION: 697 s = node.getContentDescription(); 698 if (s == null || !s.toString().toLowerCase() 699 .contains(getString(criterion).toLowerCase())) { 700 return false; 701 } 702 break; 703 case UiSelector.SELECTOR_START_DESCRIPTION: 704 s = node.getContentDescription(); 705 if (s == null || !s.toString().toLowerCase() 706 .startsWith(getString(criterion).toLowerCase())) { 707 return false; 708 } 709 break; 710 case UiSelector.SELECTOR_DESCRIPTION: 711 s = node.getContentDescription(); 712 if (s == null || !s.toString().contentEquals(getString(criterion))) { 713 return false; 714 } 715 break; 716 case UiSelector.SELECTOR_DESCRIPTION_REGEX: 717 s = node.getContentDescription(); 718 if (s == null || !getPattern(criterion).matcher(s).matches()) { 719 return false; 720 } 721 break; 722 case UiSelector.SELECTOR_CONTAINS_TEXT: 723 s = node.getText(); 724 if (s == null || !s.toString().toLowerCase() 725 .contains(getString(criterion).toLowerCase())) { 726 return false; 727 } 728 break; 729 case UiSelector.SELECTOR_START_TEXT: 730 s = node.getText(); 731 if (s == null || !s.toString().toLowerCase() 732 .startsWith(getString(criterion).toLowerCase())) { 733 return false; 734 } 735 break; 736 case UiSelector.SELECTOR_TEXT: 737 s = node.getText(); 738 if (s == null || !s.toString().contentEquals(getString(criterion))) { 739 return false; 740 } 741 break; 742 case UiSelector.SELECTOR_TEXT_REGEX: 743 s = node.getText(); 744 if (s == null || !getPattern(criterion).matcher(s).matches()) { 745 return false; 746 } 747 break; 748 case UiSelector.SELECTOR_ENABLED: 749 if (node.isEnabled() != getBoolean(criterion)) { 750 return false; 751 } 752 break; 753 case UiSelector.SELECTOR_FOCUSABLE: 754 if (node.isFocusable() != getBoolean(criterion)) { 755 return false; 756 } 757 break; 758 case UiSelector.SELECTOR_FOCUSED: 759 if (node.isFocused() != getBoolean(criterion)) { 760 return false; 761 } 762 break; 763 case UiSelector.SELECTOR_ID: 764 break; //TODO: do we need this for AccessibilityNodeInfo.id? 765 case UiSelector.SELECTOR_PACKAGE_NAME: 766 s = node.getPackageName(); 767 if (s == null || !s.toString().contentEquals(getString(criterion))) { 768 return false; 769 } 770 break; 771 case UiSelector.SELECTOR_PACKAGE_NAME_REGEX: 772 s = node.getPackageName(); 773 if (s == null || !getPattern(criterion).matcher(s).matches()) { 774 return false; 775 } 776 break; 777 case UiSelector.SELECTOR_SCROLLABLE: 778 if (node.isScrollable() != getBoolean(criterion)) { 779 return false; 780 } 781 break; 782 case UiSelector.SELECTOR_SELECTED: 783 if (node.isSelected() != getBoolean(criterion)) { 784 return false; 785 } 786 break; 787 case UiSelector.SELECTOR_RESOURCE_ID: 788 s = node.getViewIdResourceName(); 789 if (s == null || !s.toString().contentEquals(getString(criterion))) { 790 return false; 791 } 792 break; 793 case UiSelector.SELECTOR_RESOURCE_ID_REGEX: 794 s = node.getViewIdResourceName(); 795 if (s == null || !getPattern(criterion).matcher(s).matches()) { 796 return false; 797 } 798 break; 799 } 800 } 801 return matchOrUpdateInstance(); 802 } 803 matchOrUpdateInstance()804 private boolean matchOrUpdateInstance() { 805 int currentSelectorCounter = 0; 806 int currentSelectorInstance = 0; 807 808 // matched attributes - now check for matching instance number 809 if (mSelectorAttributes.indexOfKey(UiSelector.SELECTOR_INSTANCE) >= 0) { 810 currentSelectorInstance = 811 (Integer)mSelectorAttributes.get(UiSelector.SELECTOR_INSTANCE); 812 } 813 814 // instance is required. Add count if not already counting 815 if (mSelectorAttributes.indexOfKey(UiSelector.SELECTOR_COUNT) >= 0) { 816 currentSelectorCounter = (Integer)mSelectorAttributes.get(UiSelector.SELECTOR_COUNT); 817 } 818 819 // Verify 820 if (currentSelectorInstance == currentSelectorCounter) { 821 return true; 822 } 823 // Update count 824 if (currentSelectorInstance > currentSelectorCounter) { 825 mSelectorAttributes.put(UiSelector.SELECTOR_COUNT, ++currentSelectorCounter); 826 } 827 return false; 828 } 829 830 /** 831 * Leaf selector indicates no more child or parent selectors 832 * are declared in the this selector. 833 * @return true if is leaf. 834 */ isLeaf()835 boolean isLeaf() { 836 if (mSelectorAttributes.indexOfKey(UiSelector.SELECTOR_CHILD) < 0 && 837 mSelectorAttributes.indexOfKey(UiSelector.SELECTOR_PARENT) < 0) { 838 return true; 839 } 840 return false; 841 } 842 hasChildSelector()843 boolean hasChildSelector() { 844 if (mSelectorAttributes.indexOfKey(UiSelector.SELECTOR_CHILD) < 0) { 845 return false; 846 } 847 return true; 848 } 849 hasPatternSelector()850 boolean hasPatternSelector() { 851 if (mSelectorAttributes.indexOfKey(UiSelector.SELECTOR_PATTERN) < 0) { 852 return false; 853 } 854 return true; 855 } 856 hasContainerSelector()857 boolean hasContainerSelector() { 858 if (mSelectorAttributes.indexOfKey(UiSelector.SELECTOR_CONTAINER) < 0) { 859 return false; 860 } 861 return true; 862 } 863 hasParentSelector()864 boolean hasParentSelector() { 865 if (mSelectorAttributes.indexOfKey(UiSelector.SELECTOR_PARENT) < 0) { 866 return false; 867 } 868 return true; 869 } 870 871 /** 872 * Returns the deepest selector in the chain of possible sub selectors. 873 * A chain of selector is created when either of {@link UiSelector#childSelector(UiSelector)} 874 * or {@link UiSelector#fromParent(UiSelector)} are used once or more in the construction of 875 * a selector. 876 * @return last UiSelector in chain 877 */ getLastSubSelector()878 private UiSelector getLastSubSelector() { 879 if (mSelectorAttributes.indexOfKey(UiSelector.SELECTOR_CHILD) >= 0) { 880 UiSelector child = (UiSelector)mSelectorAttributes.get(UiSelector.SELECTOR_CHILD); 881 if (child.getLastSubSelector() == null) { 882 return child; 883 } 884 return child.getLastSubSelector(); 885 } else if (mSelectorAttributes.indexOfKey(UiSelector.SELECTOR_PARENT) >= 0) { 886 UiSelector parent = (UiSelector)mSelectorAttributes.get(UiSelector.SELECTOR_PARENT); 887 if (parent.getLastSubSelector() == null) { 888 return parent; 889 } 890 return parent.getLastSubSelector(); 891 } 892 return this; 893 } 894 895 @Override toString()896 public String toString() { 897 return dumpToString(true); 898 } 899 dumpToString(boolean all)900 String dumpToString(boolean all) { 901 StringBuilder builder = new StringBuilder(); 902 builder.append(UiSelector.class.getSimpleName() + "["); 903 final int criterionCount = mSelectorAttributes.size(); 904 for (int i = 0; i < criterionCount; i++) { 905 if (i > 0) { 906 builder.append(", "); 907 } 908 final int criterion = mSelectorAttributes.keyAt(i); 909 switch (criterion) { 910 case SELECTOR_TEXT: 911 builder.append("TEXT=").append(mSelectorAttributes.valueAt(i)); 912 break; 913 case SELECTOR_TEXT_REGEX: 914 builder.append("TEXT_REGEX=").append(mSelectorAttributes.valueAt(i)); 915 break; 916 case SELECTOR_START_TEXT: 917 builder.append("START_TEXT=").append(mSelectorAttributes.valueAt(i)); 918 break; 919 case SELECTOR_CONTAINS_TEXT: 920 builder.append("CONTAINS_TEXT=").append(mSelectorAttributes.valueAt(i)); 921 break; 922 case SELECTOR_CLASS: 923 builder.append("CLASS=").append(mSelectorAttributes.valueAt(i)); 924 break; 925 case SELECTOR_CLASS_REGEX: 926 builder.append("CLASS_REGEX=").append(mSelectorAttributes.valueAt(i)); 927 break; 928 case SELECTOR_DESCRIPTION: 929 builder.append("DESCRIPTION=").append(mSelectorAttributes.valueAt(i)); 930 break; 931 case SELECTOR_DESCRIPTION_REGEX: 932 builder.append("DESCRIPTION_REGEX=").append(mSelectorAttributes.valueAt(i)); 933 break; 934 case SELECTOR_START_DESCRIPTION: 935 builder.append("START_DESCRIPTION=").append(mSelectorAttributes.valueAt(i)); 936 break; 937 case SELECTOR_CONTAINS_DESCRIPTION: 938 builder.append("CONTAINS_DESCRIPTION=").append(mSelectorAttributes.valueAt(i)); 939 break; 940 case SELECTOR_INDEX: 941 builder.append("INDEX=").append(mSelectorAttributes.valueAt(i)); 942 break; 943 case SELECTOR_INSTANCE: 944 builder.append("INSTANCE=").append(mSelectorAttributes.valueAt(i)); 945 break; 946 case SELECTOR_ENABLED: 947 builder.append("ENABLED=").append(mSelectorAttributes.valueAt(i)); 948 break; 949 case SELECTOR_FOCUSED: 950 builder.append("FOCUSED=").append(mSelectorAttributes.valueAt(i)); 951 break; 952 case SELECTOR_FOCUSABLE: 953 builder.append("FOCUSABLE=").append(mSelectorAttributes.valueAt(i)); 954 break; 955 case SELECTOR_SCROLLABLE: 956 builder.append("SCROLLABLE=").append(mSelectorAttributes.valueAt(i)); 957 break; 958 case SELECTOR_CLICKABLE: 959 builder.append("CLICKABLE=").append(mSelectorAttributes.valueAt(i)); 960 break; 961 case SELECTOR_CHECKABLE: 962 builder.append("CHECKABLE=").append(mSelectorAttributes.valueAt(i)); 963 break; 964 case SELECTOR_LONG_CLICKABLE: 965 builder.append("LONG_CLICKABLE=").append(mSelectorAttributes.valueAt(i)); 966 break; 967 case SELECTOR_CHECKED: 968 builder.append("CHECKED=").append(mSelectorAttributes.valueAt(i)); 969 break; 970 case SELECTOR_SELECTED: 971 builder.append("SELECTED=").append(mSelectorAttributes.valueAt(i)); 972 break; 973 case SELECTOR_ID: 974 builder.append("ID=").append(mSelectorAttributes.valueAt(i)); 975 break; 976 case SELECTOR_CHILD: 977 if (all) 978 builder.append("CHILD=").append(mSelectorAttributes.valueAt(i)); 979 else 980 builder.append("CHILD[..]"); 981 break; 982 case SELECTOR_PATTERN: 983 if (all) 984 builder.append("PATTERN=").append(mSelectorAttributes.valueAt(i)); 985 else 986 builder.append("PATTERN[..]"); 987 break; 988 case SELECTOR_CONTAINER: 989 if (all) 990 builder.append("CONTAINER=").append(mSelectorAttributes.valueAt(i)); 991 else 992 builder.append("CONTAINER[..]"); 993 break; 994 case SELECTOR_PARENT: 995 if (all) 996 builder.append("PARENT=").append(mSelectorAttributes.valueAt(i)); 997 else 998 builder.append("PARENT[..]"); 999 break; 1000 case SELECTOR_COUNT: 1001 builder.append("COUNT=").append(mSelectorAttributes.valueAt(i)); 1002 break; 1003 case SELECTOR_PACKAGE_NAME: 1004 builder.append("PACKAGE NAME=").append(mSelectorAttributes.valueAt(i)); 1005 break; 1006 case SELECTOR_PACKAGE_NAME_REGEX: 1007 builder.append("PACKAGE_NAME_REGEX=").append(mSelectorAttributes.valueAt(i)); 1008 break; 1009 case SELECTOR_RESOURCE_ID: 1010 builder.append("RESOURCE_ID=").append(mSelectorAttributes.valueAt(i)); 1011 break; 1012 case SELECTOR_RESOURCE_ID_REGEX: 1013 builder.append("RESOURCE_ID_REGEX=").append(mSelectorAttributes.valueAt(i)); 1014 break; 1015 default: 1016 builder.append("UNDEFINED="+criterion+" ").append(mSelectorAttributes.valueAt(i)); 1017 } 1018 } 1019 builder.append("]"); 1020 return builder.toString(); 1021 } 1022 } 1023