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