157e46577852ffa1dde4662f6018f7fbcfacb6148Thanh Le/*
21194ec356a16f3c6dcf408289e36e42c149d6dc8Kevin Jin * Copyright (C) 2013 DroidDriver committers
357e46577852ffa1dde4662f6018f7fbcfacb6148Thanh Le *
457e46577852ffa1dde4662f6018f7fbcfacb6148Thanh Le * Licensed under the Apache License, Version 2.0 (the "License");
557e46577852ffa1dde4662f6018f7fbcfacb6148Thanh Le * you may not use this file except in compliance with the License.
657e46577852ffa1dde4662f6018f7fbcfacb6148Thanh Le * You may obtain a copy of the License at
757e46577852ffa1dde4662f6018f7fbcfacb6148Thanh Le *
857e46577852ffa1dde4662f6018f7fbcfacb6148Thanh Le *      http://www.apache.org/licenses/LICENSE-2.0
957e46577852ffa1dde4662f6018f7fbcfacb6148Thanh Le *
1057e46577852ffa1dde4662f6018f7fbcfacb6148Thanh Le * Unless required by applicable law or agreed to in writing, software
1157e46577852ffa1dde4662f6018f7fbcfacb6148Thanh Le * distributed under the License is distributed on an "AS IS" BASIS,
1257e46577852ffa1dde4662f6018f7fbcfacb6148Thanh Le * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1357e46577852ffa1dde4662f6018f7fbcfacb6148Thanh Le * See the License for the specific language governing permissions and
1457e46577852ffa1dde4662f6018f7fbcfacb6148Thanh Le * limitations under the License.
1557e46577852ffa1dde4662f6018f7fbcfacb6148Thanh Le */
1657e46577852ffa1dde4662f6018f7fbcfacb6148Thanh Le
171194ec356a16f3c6dcf408289e36e42c149d6dc8Kevin Jinpackage com.google.android.droiddriver.uiautomation;
1857e46577852ffa1dde4662f6018f7fbcfacb6148Thanh Le
197dde4b200c490587409e0301e58261210e7a5896Tony Wickhamimport android.app.Instrumentation;
2074676fdd3c8a9e599eddd13bea56898674d9916aKevin Jinimport android.app.UiAutomation;
2174676fdd3c8a9e599eddd13bea56898674d9916aKevin Jinimport android.content.Context;
2274676fdd3c8a9e599eddd13bea56898674d9916aKevin Jinimport android.os.SystemClock;
2374676fdd3c8a9e599eddd13bea56898674d9916aKevin Jinimport android.view.accessibility.AccessibilityEvent;
2474676fdd3c8a9e599eddd13bea56898674d9916aKevin Jinimport android.view.accessibility.AccessibilityManager;
2574676fdd3c8a9e599eddd13bea56898674d9916aKevin Jinimport android.view.accessibility.AccessibilityNodeInfo;
2639f9784bdf3d4663263c344139454736af13e111Kevin Jin
2774676fdd3c8a9e599eddd13bea56898674d9916aKevin Jinimport com.google.android.droiddriver.actions.InputInjector;
2874676fdd3c8a9e599eddd13bea56898674d9916aKevin Jinimport com.google.android.droiddriver.base.BaseDroidDriver;
2974676fdd3c8a9e599eddd13bea56898674d9916aKevin Jinimport com.google.android.droiddriver.exceptions.TimeoutException;
3074676fdd3c8a9e599eddd13bea56898674d9916aKevin Jinimport com.google.android.droiddriver.uiautomation.UiAutomationContext.UiAutomationCallable;
3174676fdd3c8a9e599eddd13bea56898674d9916aKevin Jinimport com.google.android.droiddriver.util.Logs;
3257e46577852ffa1dde4662f6018f7fbcfacb6148Thanh Le
3357e46577852ffa1dde4662f6018f7fbcfacb6148Thanh Le/**
34a738fe74f57f48dde2dd7a28479bab3f5441dadbKevin Jin * Implementation of DroidDriver that gets attributes via the Accessibility API
35a738fe74f57f48dde2dd7a28479bab3f5441dadbKevin Jin * and is acted upon via synthesized events.
3657e46577852ffa1dde4662f6018f7fbcfacb6148Thanh Le */
3774676fdd3c8a9e599eddd13bea56898674d9916aKevin Jinpublic class UiAutomationDriver extends BaseDroidDriver<AccessibilityNodeInfo, UiAutomationElement> {
3874676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin  // TODO: magic const from UiAutomator, but may not be useful
3974676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin  /**
4074676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin   * This value has the greatest bearing on the appearance of test execution
4174676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin   * speeds. This value is used as the minimum time to wait before considering
4274676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin   * the UI idle after each action.
4374676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin   */
4474676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin  private static final long QUIET_TIME_TO_BE_CONSIDERD_IDLE_STATE = 500;// ms
4574676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin
4674676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin  private final UiAutomationContext context;
4774676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin  private final InputInjector injector;
4874676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin  private final UiAutomationUiDevice uiDevice;
49988386cd9cc46bf5399846a414c09e0af48b1e5aKevin Jin  private AccessibilityNodeInfoCacheClearer clearer =
50988386cd9cc46bf5399846a414c09e0af48b1e5aKevin Jin      new WindowStateAccessibilityNodeInfoCacheClearer();
5174676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin
527dde4b200c490587409e0301e58261210e7a5896Tony Wickham  public UiAutomationDriver(Instrumentation instrumentation) {
5374676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin    context = new UiAutomationContext(instrumentation, this);
5474676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin    injector = new UiAutomationInputInjector(context);
5574676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin    uiDevice = new UiAutomationUiDevice(context);
5674676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin  }
5774676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin
5874676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin  @Override
5974676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin  public InputInjector getInjector() {
6074676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin    return injector;
6174676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin  }
6274676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin
6374676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin  @Override
6474676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin  protected UiAutomationElement newRootElement() {
6574676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin    return context.newRootElement(getRootNode());
6674676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin  }
6774676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin
6874676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin  @Override
6974676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin  protected UiAutomationElement newUiElement(AccessibilityNodeInfo rawElement,
7074676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin      UiAutomationElement parent) {
7174676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin    return new UiAutomationElement(context, rawElement, parent);
7274676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin  }
7374676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin
7474676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin  private AccessibilityNodeInfo getRootNode() {
7574676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin    final long timeoutMillis = getPoller().getTimeoutMillis();
7674676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin    context.callUiAutomation(new UiAutomationCallable<Void>() {
7774676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin      @Override
7874676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin      public Void call(UiAutomation uiAutomation) {
7974676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin        try {
8074676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin          uiAutomation.waitForIdle(QUIET_TIME_TO_BE_CONSIDERD_IDLE_STATE, timeoutMillis);
8174676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin          return null;
8274676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin        } catch (java.util.concurrent.TimeoutException e) {
8374676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin          throw new TimeoutException(e);
8474676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin        }
8574676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin      }
8674676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin    });
8774676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin
8874676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin    long end = SystemClock.uptimeMillis() + timeoutMillis;
8974676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin    while (true) {
9074676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin      AccessibilityNodeInfo root =
9174676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin          context.callUiAutomation(new UiAutomationCallable<AccessibilityNodeInfo>() {
9274676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin            @Override
9374676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin            public AccessibilityNodeInfo call(UiAutomation uiAutomation) {
9474676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin              return uiAutomation.getRootInActiveWindow();
9574676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin            }
9674676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin          });
9774676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin      if (root != null) {
9874676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin        return root;
9974676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin      }
10074676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin      long remainingMillis = end - SystemClock.uptimeMillis();
10174676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin      if (remainingMillis < 0) {
10274676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin        throw new TimeoutException(
10374676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin            String.format("Timed out after %d milliseconds waiting for root AccessibilityNodeInfo",
10474676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin                timeoutMillis));
10574676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin      }
10674676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin      SystemClock.sleep(Math.min(250, remainingMillis));
10774676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin    }
10874676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin  }
10974676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin
11074676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin  /**
11174676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin   * Some widgets fail to trigger some AccessibilityEvent's after actions,
11274676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin   * resulting in stale AccessibilityNodeInfo's. As a work-around, force to
11374676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin   * clear the AccessibilityNodeInfoCache.
11474676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin   */
11574676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin  public void clearAccessibilityNodeInfoCache() {
11674676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin    Logs.call(this, "clearAccessibilityNodeInfoCache");
117988386cd9cc46bf5399846a414c09e0af48b1e5aKevin Jin    clearer.clearAccessibilityNodeInfoCache(this);
118988386cd9cc46bf5399846a414c09e0af48b1e5aKevin Jin  }
119988386cd9cc46bf5399846a414c09e0af48b1e5aKevin Jin
120988386cd9cc46bf5399846a414c09e0af48b1e5aKevin Jin  public interface AccessibilityNodeInfoCacheClearer {
121988386cd9cc46bf5399846a414c09e0af48b1e5aKevin Jin    void clearAccessibilityNodeInfoCache(UiAutomationDriver driver);
122988386cd9cc46bf5399846a414c09e0af48b1e5aKevin Jin  }
123988386cd9cc46bf5399846a414c09e0af48b1e5aKevin Jin
124988386cd9cc46bf5399846a414c09e0af48b1e5aKevin Jin  /**
125988386cd9cc46bf5399846a414c09e0af48b1e5aKevin Jin   * Clears AccessibilityNodeInfoCache by turning screen off then on.
126988386cd9cc46bf5399846a414c09e0af48b1e5aKevin Jin   */
127988386cd9cc46bf5399846a414c09e0af48b1e5aKevin Jin  public static class ScreenOffAccessibilityNodeInfoCacheClearer implements
128988386cd9cc46bf5399846a414c09e0af48b1e5aKevin Jin      AccessibilityNodeInfoCacheClearer {
129988386cd9cc46bf5399846a414c09e0af48b1e5aKevin Jin    public void clearAccessibilityNodeInfoCache(UiAutomationDriver driver) {
130988386cd9cc46bf5399846a414c09e0af48b1e5aKevin Jin      driver.getUiDevice().sleep();
131988386cd9cc46bf5399846a414c09e0af48b1e5aKevin Jin      driver.getUiDevice().wakeUp();
132988386cd9cc46bf5399846a414c09e0af48b1e5aKevin Jin    }
13374676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin  }
13474676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin
13574676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin  /**
136988386cd9cc46bf5399846a414c09e0af48b1e5aKevin Jin   * Clears AccessibilityNodeInfoCache by exploiting an implementation detail of
137988386cd9cc46bf5399846a414c09e0af48b1e5aKevin Jin   * AccessibilityNodeInfoCache. This is a hack; use it at your own discretion.
13874676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin   */
139988386cd9cc46bf5399846a414c09e0af48b1e5aKevin Jin  public static class WindowStateAccessibilityNodeInfoCacheClearer implements
140988386cd9cc46bf5399846a414c09e0af48b1e5aKevin Jin      AccessibilityNodeInfoCacheClearer {
141988386cd9cc46bf5399846a414c09e0af48b1e5aKevin Jin    public void clearAccessibilityNodeInfoCache(UiAutomationDriver driver) {
142988386cd9cc46bf5399846a414c09e0af48b1e5aKevin Jin      AccessibilityManager accessibilityManager =
143988386cd9cc46bf5399846a414c09e0af48b1e5aKevin Jin          (AccessibilityManager) driver.context.getInstrumentation().getTargetContext()
144988386cd9cc46bf5399846a414c09e0af48b1e5aKevin Jin              .getSystemService(Context.ACCESSIBILITY_SERVICE);
145988386cd9cc46bf5399846a414c09e0af48b1e5aKevin Jin      accessibilityManager.sendAccessibilityEvent(AccessibilityEvent
146988386cd9cc46bf5399846a414c09e0af48b1e5aKevin Jin          .obtain(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED));
147988386cd9cc46bf5399846a414c09e0af48b1e5aKevin Jin    }
148988386cd9cc46bf5399846a414c09e0af48b1e5aKevin Jin  }
149988386cd9cc46bf5399846a414c09e0af48b1e5aKevin Jin
150988386cd9cc46bf5399846a414c09e0af48b1e5aKevin Jin  public void setAccessibilityNodeInfoCacheClearer(AccessibilityNodeInfoCacheClearer clearer) {
151988386cd9cc46bf5399846a414c09e0af48b1e5aKevin Jin    this.clearer = clearer;
15270e34108e0fc19277e642aef3b36b65b8e254899Kevin Jin  }
15370e34108e0fc19277e642aef3b36b65b8e254899Kevin Jin
15470e34108e0fc19277e642aef3b36b65b8e254899Kevin Jin  @Override
15574676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin  public UiAutomationUiDevice getUiDevice() {
15674676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin    return uiDevice;
15770e34108e0fc19277e642aef3b36b65b8e254899Kevin Jin  }
15857e46577852ffa1dde4662f6018f7fbcfacb6148Thanh Le}
159