DynamicSentinelStrategy.java revision 9c92f46280cf3943701e75349833c68b584992e2
1f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin/*
2f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin * Copyright (C) 2013 DroidDriver committers
3f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin *
4f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin * Licensed under the Apache License, Version 2.0 (the "License");
5f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin * you may not use this file except in compliance with the License.
6f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin * You may obtain a copy of the License at
7f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin *
8f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin *      http://www.apache.org/licenses/LICENSE-2.0
9f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin *
10f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin * Unless required by applicable law or agreed to in writing, software
11f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin * distributed under the License is distributed on an "AS IS" BASIS,
12f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin * See the License for the specific language governing permissions and
14f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin * limitations under the License.
15f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin */
16f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jinpackage com.google.android.droiddriver.scroll;
17f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin
18f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jinimport android.util.Log;
19f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin
20f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jinimport com.google.android.droiddriver.DroidDriver;
21f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jinimport com.google.android.droiddriver.UiElement;
22f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jinimport com.google.android.droiddriver.exceptions.ElementNotFoundException;
23dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jinimport com.google.android.droiddriver.finders.By;
24f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jinimport com.google.android.droiddriver.finders.Finder;
25b5194043e9f0a1319dc7251f829febab3c76e277Kevin Jinimport com.google.android.droiddriver.scroll.Direction.DirectionConverter;
2629d66eeee5d30f7db747cceeb84defec961b4125Kevin Jinimport com.google.android.droiddriver.scroll.Direction.PhysicalDirection;
27f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jinimport com.google.android.droiddriver.util.Logs;
28f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jinimport com.google.common.base.Objects;
29f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin
30f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin/**
31f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin * Determines whether scrolling is possible by checking whether the sentinel
320319e7c14a536a11851cc30cfa57241ce90fec11Kevin Jin * child is updated after scrolling. Use this when {@link UiElement#getChildren}
330319e7c14a536a11851cc30cfa57241ce90fec11Kevin Jin * is not reliable. This can happen, for instance, when UiAutomationDriver is
340319e7c14a536a11851cc30cfa57241ce90fec11Kevin Jin * used, which skips invisible children, or in the case of dynamic list, which
350319e7c14a536a11851cc30cfa57241ce90fec11Kevin Jin * shows more items when scrolling beyond the end.
36f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin */
379c92f46280cf3943701e75349833c68b584992e2Kevin Jinpublic class DynamicSentinelStrategy extends BaseSentinelStrategy {
38f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin
39f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin  /**
40f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin   * Interface for determining whether sentinel is updated.
41f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin   */
42f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin  public static interface IsUpdatedStrategy {
43f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin    /**
44f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin     * Returns whether {@code newSentinel} is updated from {@code oldSentinel}.
45f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin     */
46f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin    boolean isSentinelUpdated(UiElement newSentinel, UiElement oldSentinel);
47f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin
48f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin    /**
49f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin     * {@inheritDoc}
50f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin     *
51f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin     * <p>
52f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin     * It is recommended that this method return a description to help
53f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin     * debugging.
54f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin     */
55f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin    @Override
56f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin    String toString();
57f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin  }
58f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin
59f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin  /**
60f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin   * Determines whether the sentinel is updated by checking a single unique
610319e7c14a536a11851cc30cfa57241ce90fec11Kevin Jin   * String attribute of a descendant element of the sentinel (or itself).
62f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin   */
63f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin  public static abstract class SingleStringUpdated implements IsUpdatedStrategy {
64f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin    private final Finder uniqueStringFinder;
65f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin
66f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin    /**
67f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin     * @param uniqueStringFinder a Finder relative to the sentinel that finds
680319e7c14a536a11851cc30cfa57241ce90fec11Kevin Jin     *        its descendant or self which contains a unique String.
69f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin     */
70f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin    public SingleStringUpdated(Finder uniqueStringFinder) {
71f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin      this.uniqueStringFinder = uniqueStringFinder;
72f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin    }
73f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin
74f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin    /**
750319e7c14a536a11851cc30cfa57241ce90fec11Kevin Jin     * @param uniqueStringElement the descendant or self that contains the
760319e7c14a536a11851cc30cfa57241ce90fec11Kevin Jin     *        unique String
77f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin     * @return the unique String
78f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin     */
790319e7c14a536a11851cc30cfa57241ce90fec11Kevin Jin    protected abstract String getUniqueString(UiElement uniqueStringElement);
80f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin
81f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin    private String getUniqueStringFromSentinel(UiElement sentinel) {
82f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin      try {
83f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin        return getUniqueString(uniqueStringFinder.find(sentinel));
84f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin      } catch (ElementNotFoundException e) {
85f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin        return null;
86f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin      }
87f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin    }
88f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin
89f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin    @Override
90f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin    public boolean isSentinelUpdated(UiElement newSentinel, UiElement oldSentinel) {
9170e34108e0fc19277e642aef3b36b65b8e254899Kevin Jin      // If the sentinel moved, scrolling has some effect. This is both an
9270e34108e0fc19277e642aef3b36b65b8e254899Kevin Jin      // optimization - getBounds is cheaper than find - and necessary in
9370e34108e0fc19277e642aef3b36b65b8e254899Kevin Jin      // certain cases, e.g. user is looking for a sibling of the unique string;
9470e34108e0fc19277e642aef3b36b65b8e254899Kevin Jin      // the scroll is close to the end therefore the unique string does not
9570e34108e0fc19277e642aef3b36b65b8e254899Kevin Jin      // change, but the target could be revealed.
9670e34108e0fc19277e642aef3b36b65b8e254899Kevin Jin      if (!newSentinel.getBounds().equals(oldSentinel.getBounds())) {
9770e34108e0fc19277e642aef3b36b65b8e254899Kevin Jin        return true;
9870e34108e0fc19277e642aef3b36b65b8e254899Kevin Jin      }
9970e34108e0fc19277e642aef3b36b65b8e254899Kevin Jin
100f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin      String newString = getUniqueStringFromSentinel(newSentinel);
101a5bb27d69e8501b7c8321b838646d0b8f6fa0d05Kevin Jin      // A legitimate case for newString being null is when newSentinel is
102a5bb27d69e8501b7c8321b838646d0b8f6fa0d05Kevin Jin      // partially shown. We return true to allow further scrolling. But program
1039c92f46280cf3943701e75349833c68b584992e2Kevin Jin      // error could also cause this, e.g. a bad choice of Getter, which
104a5bb27d69e8501b7c8321b838646d0b8f6fa0d05Kevin Jin      // results in unnecessary scroll actions that have no visual effect. This
105a5bb27d69e8501b7c8321b838646d0b8f6fa0d05Kevin Jin      // log helps troubleshooting in the latter case.
106f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin      if (newString == null) {
107a5bb27d69e8501b7c8321b838646d0b8f6fa0d05Kevin Jin        Logs.logfmt(Log.WARN, "Unique String is null: sentinel=%s, uniqueStringFinder=%s",
108a5bb27d69e8501b7c8321b838646d0b8f6fa0d05Kevin Jin            newSentinel, uniqueStringFinder);
109f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin        return true;
110f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin      }
111f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin      if (newString.equals(getUniqueStringFromSentinel(oldSentinel))) {
112f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin        Logs.log(Log.INFO, "Unique String is not updated: " + newString);
113f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin        return false;
114f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin      }
115f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin      return true;
116f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin    }
117f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin
118f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin    @Override
119f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin    public String toString() {
120f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin      return Objects.toStringHelper(this).addValue(uniqueStringFinder).toString();
121f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin    }
122f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin  }
123f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin
124f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin  /**
1250319e7c14a536a11851cc30cfa57241ce90fec11Kevin Jin   * Determines whether the sentinel is updated by checking the text of a
1260319e7c14a536a11851cc30cfa57241ce90fec11Kevin Jin   * descendant element of the sentinel (or itself).
127f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin   */
128f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin  public static class TextUpdated extends SingleStringUpdated {
129f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin    public TextUpdated(Finder uniqueStringFinder) {
130f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin      super(uniqueStringFinder);
131f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin    }
132f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin
133f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin    @Override
1340319e7c14a536a11851cc30cfa57241ce90fec11Kevin Jin    protected String getUniqueString(UiElement uniqueStringElement) {
1350319e7c14a536a11851cc30cfa57241ce90fec11Kevin Jin      return uniqueStringElement.getText();
136f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin    }
137f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin  }
138f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin
139f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin  /**
140f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin   * Determines whether the sentinel is updated by checking the content
1410319e7c14a536a11851cc30cfa57241ce90fec11Kevin Jin   * description of a descendant element of the sentinel (or itself).
142f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin   */
143f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin  public static class ContentDescriptionUpdated extends SingleStringUpdated {
144f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin    public ContentDescriptionUpdated(Finder uniqueStringFinder) {
145f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin      super(uniqueStringFinder);
146f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin    }
147f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin
148f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin    @Override
1490319e7c14a536a11851cc30cfa57241ce90fec11Kevin Jin    protected String getUniqueString(UiElement uniqueStringElement) {
1500319e7c14a536a11851cc30cfa57241ce90fec11Kevin Jin      return uniqueStringElement.getContentDescription();
151f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin    }
152f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin  }
153f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin
154dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin  /**
155dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin   * Determines whether the sentinel is updated by checking the resource-id of a
156dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin   * descendant element of the sentinel (often itself). This is useful when the
157dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin   * children of the container are heterogeneous -- they don't have a common
158dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin   * pattern to get a unique string.
159dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin   */
160dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin  public static class ResourceIdUpdated extends SingleStringUpdated {
161dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin    /**
162dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin     * Uses the resource-id of the sentinel itself.
163dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin     */
164dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin    public static final ResourceIdUpdated SELF = new ResourceIdUpdated(By.any());
165dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin
166dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin    public ResourceIdUpdated(Finder uniqueStringFinder) {
167dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin      super(uniqueStringFinder);
168dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin    }
169dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin
170dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin    @Override
171dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin    protected String getUniqueString(UiElement uniqueStringElement) {
172dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin      return uniqueStringElement.getResourceId();
173dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin    }
174dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin  }
175dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin
176f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin  private final IsUpdatedStrategy isUpdatedStrategy;
177f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin
178f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin  /**
1799c92f46280cf3943701e75349833c68b584992e2Kevin Jin   * Constructs with {@code Getter}s that decorate the given {@code Getter}s
1809c92f46280cf3943701e75349833c68b584992e2Kevin Jin   * with {@link UiElement#VISIBLE}, and the given {@code isUpdatedStrategy} and
1819c92f46280cf3943701e75349833c68b584992e2Kevin Jin   * {@code directionConverter}. Be careful with {@code Getter}s: the sentinel
1829c92f46280cf3943701e75349833c68b584992e2Kevin Jin   * after each scroll should be unique.
183f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin   */
1849c92f46280cf3943701e75349833c68b584992e2Kevin Jin  public DynamicSentinelStrategy(IsUpdatedStrategy isUpdatedStrategy, Getter backwardGetter,
1859c92f46280cf3943701e75349833c68b584992e2Kevin Jin      Getter forwardGetter, DirectionConverter directionConverter) {
1869c92f46280cf3943701e75349833c68b584992e2Kevin Jin    super(new MorePredicateGetter(backwardGetter, UiElement.VISIBLE, "VISIBLE_"),
1879c92f46280cf3943701e75349833c68b584992e2Kevin Jin        new MorePredicateGetter(forwardGetter, UiElement.VISIBLE, "VISIBLE_"), directionConverter);
188f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin    this.isUpdatedStrategy = isUpdatedStrategy;
189f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin  }
190f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin
191f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin  /**
192b5194043e9f0a1319dc7251f829febab3c76e277Kevin Jin   * Defaults to the standard {@link DirectionConverter}.
193f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin   */
1949c92f46280cf3943701e75349833c68b584992e2Kevin Jin  public DynamicSentinelStrategy(IsUpdatedStrategy isUpdatedStrategy, Getter backwardGetter,
1959c92f46280cf3943701e75349833c68b584992e2Kevin Jin      Getter forwardGetter) {
1969c92f46280cf3943701e75349833c68b584992e2Kevin Jin    this(isUpdatedStrategy, backwardGetter, forwardGetter, DirectionConverter.STANDARD_CONVERTER);
197f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin  }
198f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin
199f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin  /**
200f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin   * Defaults to LAST_CHILD_GETTER for forward scrolling, and the standard
201b5194043e9f0a1319dc7251f829febab3c76e277Kevin Jin   * {@link DirectionConverter}.
202f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin   */
2039c92f46280cf3943701e75349833c68b584992e2Kevin Jin  public DynamicSentinelStrategy(IsUpdatedStrategy isUpdatedStrategy, Getter backwardGetter) {
2049c92f46280cf3943701e75349833c68b584992e2Kevin Jin    this(isUpdatedStrategy, backwardGetter, LAST_CHILD_GETTER,
205b5194043e9f0a1319dc7251f829febab3c76e277Kevin Jin        DirectionConverter.STANDARD_CONVERTER);
206f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin  }
207f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin
208f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin  @Override
2090319e7c14a536a11851cc30cfa57241ce90fec11Kevin Jin  public boolean scroll(DroidDriver driver, Finder containerFinder, PhysicalDirection direction) {
2100319e7c14a536a11851cc30cfa57241ce90fec11Kevin Jin    UiElement oldSentinel = getSentinel(driver, containerFinder, direction);
2116f160bb942de53103e4f4ed54acaafe2da629fcfKevin Jin    oldSentinel.getParent().scroll(direction);
2120319e7c14a536a11851cc30cfa57241ce90fec11Kevin Jin    UiElement newSentinel = getSentinel(driver, containerFinder, direction);
213f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin    return isUpdatedStrategy.isSentinelUpdated(newSentinel, oldSentinel);
214f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin  }
215f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin
216f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin  @Override
217f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin  public String toString() {
218f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin    return String.format("DynamicSentinelStrategy{%s, isUpdatedStrategy=%s}", super.toString(),
219f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin        isUpdatedStrategy);
220f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin  }
221f9c6c5063b38b623679e47d7095cccddb0481319Kevin Jin}
222