Home | History | Annotate | Download | only in scroll
      1 /*
      2  * Copyright (C) 2013 DroidDriver committers
      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 package io.appium.droiddriver.scroll;
     17 
     18 import android.util.Log;
     19 
     20 import java.util.List;
     21 
     22 import io.appium.droiddriver.DroidDriver;
     23 import io.appium.droiddriver.UiElement;
     24 import io.appium.droiddriver.exceptions.ElementNotFoundException;
     25 import io.appium.droiddriver.finders.By;
     26 import io.appium.droiddriver.finders.Finder;
     27 import io.appium.droiddriver.finders.Predicate;
     28 import io.appium.droiddriver.finders.Predicates;
     29 import io.appium.droiddriver.scroll.Direction.DirectionConverter;
     30 import io.appium.droiddriver.scroll.Direction.LogicalDirection;
     31 import io.appium.droiddriver.scroll.Direction.PhysicalDirection;
     32 import io.appium.droiddriver.util.Logs;
     33 
     34 /**
     35  * A {@link ScrollStepStrategy} that determines whether scrolling is possible
     36  * based on a sentinel.
     37  */
     38 public abstract class SentinelStrategy implements ScrollStepStrategy {
     39   /**
     40    * A {@link Finder} for sentinel. Note that unlike {@link Finder}, invisible
     41    * UiElements are not skipped by default.
     42    */
     43   public static abstract class Getter implements Finder {
     44     protected final Predicate<? super UiElement> predicate;
     45 
     46     protected Getter() {
     47       // Include invisible children by default.
     48       this(null);
     49     }
     50 
     51     protected Getter(Predicate<? super UiElement> predicate) {
     52       this.predicate = predicate;
     53     }
     54 
     55     /**
     56      * Gets the sentinel, which must be an immediate child of {@code container}
     57      * - not a descendant. Note sentinel may not exist if {@code container} has
     58      * not finished updating.
     59      */
     60     @Override
     61     public UiElement find(UiElement container) {
     62       UiElement sentinel = getSentinel(container.getChildren(predicate));
     63       if (sentinel == null) {
     64         throw new ElementNotFoundException(this);
     65       }
     66       Logs.log(Log.INFO, "Found sentinel: " + sentinel);
     67       return sentinel;
     68     }
     69 
     70 
     71     protected abstract UiElement getSentinel(List<? extends UiElement> children);
     72 
     73     @Override
     74     public abstract String toString();
     75   }
     76 
     77   /**
     78    * Returns the first child as the sentinel.
     79    */
     80   public static final Getter FIRST_CHILD_GETTER = new Getter() {
     81     @Override
     82     protected UiElement getSentinel(List<? extends UiElement> children) {
     83       return children.isEmpty() ? null : children.get(0);
     84     }
     85 
     86     @Override
     87     public String toString() {
     88       return "FIRST_CHILD";
     89     }
     90   };
     91   /**
     92    * Returns the last child as the sentinel.
     93    */
     94   public static final Getter LAST_CHILD_GETTER = new Getter() {
     95     @Override
     96     protected UiElement getSentinel(List<? extends UiElement> children) {
     97       return children.isEmpty() ? null : children.get(children.size() - 1);
     98     }
     99 
    100     @Override
    101     public String toString() {
    102       return "LAST_CHILD";
    103     }
    104   };
    105   /**
    106    * Returns the second last child as the sentinel. Useful when the activity
    107    * always shows the last child as an anchor (for example a footer).
    108    * <p>
    109    * Sometimes uiautomatorviewer may not show the anchor as the last child, due
    110    * to the reordering by layout described in {@link UiElement#getChildren}.
    111    * This is not a problem with UiAutomationDriver because it sees the same as
    112    * uiautomatorviewer does, but could be a problem with InstrumentationDriver.
    113    * </p>
    114    */
    115   public static final Getter SECOND_LAST_CHILD_GETTER = new Getter() {
    116     @Override
    117     protected UiElement getSentinel(List<? extends UiElement> children) {
    118       return children.size() < 2 ? null : children.get(children.size() - 2);
    119     }
    120 
    121     @Override
    122     public String toString() {
    123       return "SECOND_LAST_CHILD";
    124     }
    125   };
    126   /**
    127    * Returns the second child as the sentinel. Useful when the activity shows a
    128    * fixed first child.
    129    */
    130   public static final Getter SECOND_CHILD_GETTER = new Getter() {
    131     @Override
    132     protected UiElement getSentinel(List<? extends UiElement> children) {
    133       return children.size() <= 1 ? null : children.get(1);
    134     }
    135 
    136     @Override
    137     public String toString() {
    138       return "SECOND_CHILD";
    139     }
    140   };
    141 
    142   /**
    143    * Decorates a {@link Getter} by adding another {@link Predicate}.
    144    */
    145   public static class MorePredicateGetter extends Getter {
    146     private final Getter original;
    147 
    148     public MorePredicateGetter(Getter original, Predicate<? super UiElement> extraPredicate) {
    149       super(Predicates.allOf(original.predicate, extraPredicate));
    150       this.original = original;
    151     }
    152 
    153     @Override
    154     protected UiElement getSentinel(List<? extends UiElement> children) {
    155       return original.getSentinel(children);
    156     }
    157 
    158     @Override
    159     public String toString() {
    160       return predicate.toString() + " " + original;
    161     }
    162   }
    163 
    164   private final Getter backwardGetter;
    165   private final Getter forwardGetter;
    166   private final DirectionConverter directionConverter;
    167 
    168   protected SentinelStrategy(Getter backwardGetter, Getter forwardGetter,
    169       DirectionConverter directionConverter) {
    170     this.backwardGetter = backwardGetter;
    171     this.forwardGetter = forwardGetter;
    172     this.directionConverter = directionConverter;
    173   }
    174 
    175   protected UiElement getSentinel(DroidDriver driver, Finder containerFinder,
    176       PhysicalDirection direction) {
    177     Logs.call(this, "getSentinel", driver, containerFinder, direction);
    178     Finder sentinelFinder;
    179     LogicalDirection logicalDirection = directionConverter.toLogicalDirection(direction);
    180     if (logicalDirection == LogicalDirection.BACKWARD) {
    181       sentinelFinder = By.chain(containerFinder, backwardGetter);
    182     } else {
    183       sentinelFinder = By.chain(containerFinder, forwardGetter);
    184     }
    185     return driver.on(sentinelFinder);
    186   }
    187 
    188   @Override
    189   public final DirectionConverter getDirectionConverter() {
    190     return directionConverter;
    191   }
    192 
    193   @Override
    194   public void beginScrolling(DroidDriver driver, Finder containerFinder, Finder itemFinder,
    195       PhysicalDirection direction) {}
    196 
    197   @Override
    198   public void endScrolling(DroidDriver driver, Finder containerFinder, Finder itemFinder,
    199       PhysicalDirection direction) {}
    200 
    201   @Override
    202   public String toString() {
    203     return String.format("{backwardGetter=%s, forwardGetter=%s}", backwardGetter, forwardGetter);
    204   }
    205 
    206   @Override
    207   public void doScroll(UiElement container, PhysicalDirection direction) {
    208     container.scroll(direction);
    209   }
    210 }
    211