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