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 
17 package io.appium.droiddriver.uiautomation;
18 
19 import android.annotation.TargetApi;
20 import android.app.Instrumentation;
21 import android.app.UiAutomation;
22 import android.content.Context;
23 import android.os.SystemClock;
24 import android.view.accessibility.AccessibilityEvent;
25 import android.view.accessibility.AccessibilityManager;
26 import android.view.accessibility.AccessibilityNodeInfo;
27 
28 import io.appium.droiddriver.actions.InputInjector;
29 import io.appium.droiddriver.base.BaseDroidDriver;
30 import io.appium.droiddriver.exceptions.TimeoutException;
31 import io.appium.droiddriver.uiautomation.UiAutomationContext.UiAutomationCallable;
32 import io.appium.droiddriver.util.Logs;
33 
34 /**
35  * Implementation of DroidDriver that gets attributes via the Accessibility API
36  * and is acted upon via synthesized events.
37  */
38 @TargetApi(18)
39 public class UiAutomationDriver extends BaseDroidDriver<AccessibilityNodeInfo, UiAutomationElement> {
40   // This is a magic const copied from UiAutomator.
41   /**
42    * This value has the greatest bearing on the appearance of test execution
43    * speeds. This value is used as the minimum time to wait before considering
44    * the UI idle after each action.
45    */
46   private static final long QUIET_TIME_TO_BE_CONSIDERD_IDLE_STATE = 500;// ms
47   private static long idleTimeoutMillis = QUIET_TIME_TO_BE_CONSIDERD_IDLE_STATE;
48 
49   /** Sets the {@code idleTimeoutMillis} argument for calling {@link UiAutomation#waitForIdle} */
setIdleTimeoutMillis(long idleTimeoutMillis)50   public static void setIdleTimeoutMillis(long idleTimeoutMillis) {
51     UiAutomationDriver.idleTimeoutMillis = idleTimeoutMillis;
52   }
53 
54   private final UiAutomationContext context;
55   private final InputInjector injector;
56   private final UiAutomationUiDevice uiDevice;
57   private AccessibilityNodeInfoCacheClearer clearer =
58       new WindowStateAccessibilityNodeInfoCacheClearer();
59 
UiAutomationDriver(Instrumentation instrumentation)60   public UiAutomationDriver(Instrumentation instrumentation) {
61     context = new UiAutomationContext(instrumentation, this);
62     injector = new UiAutomationInputInjector(context);
63     uiDevice = new UiAutomationUiDevice(context);
64   }
65 
66   @Override
getInjector()67   public InputInjector getInjector() {
68     return injector;
69   }
70 
71   @Override
newRootElement()72   protected UiAutomationElement newRootElement() {
73     return context.newRootElement(getRootNode());
74   }
75 
76   @Override
newUiElement(AccessibilityNodeInfo rawElement, UiAutomationElement parent)77   protected UiAutomationElement newUiElement(AccessibilityNodeInfo rawElement,
78       UiAutomationElement parent) {
79     return new UiAutomationElement(context, rawElement, parent);
80   }
81 
getRootNode()82   private AccessibilityNodeInfo getRootNode() {
83     final long timeoutMillis = getPoller().getTimeoutMillis();
84     context.callUiAutomation(new UiAutomationCallable<Void>() {
85       @Override
86       public Void call(UiAutomation uiAutomation) {
87         try {
88           uiAutomation.waitForIdle(idleTimeoutMillis, timeoutMillis);
89           return null;
90         } catch (java.util.concurrent.TimeoutException e) {
91           throw new TimeoutException(e);
92         }
93       }
94     });
95 
96     long end = SystemClock.uptimeMillis() + timeoutMillis;
97     while (true) {
98       AccessibilityNodeInfo root =
99           context.callUiAutomation(new UiAutomationCallable<AccessibilityNodeInfo>() {
100             @Override
101             public AccessibilityNodeInfo call(UiAutomation uiAutomation) {
102               return uiAutomation.getRootInActiveWindow();
103             }
104           });
105       if (root != null) {
106         return root;
107       }
108       long remainingMillis = end - SystemClock.uptimeMillis();
109       if (remainingMillis < 0) {
110         throw new TimeoutException(
111             String.format("Timed out after %d milliseconds waiting for root AccessibilityNodeInfo",
112                 timeoutMillis));
113       }
114       SystemClock.sleep(Math.min(250, remainingMillis));
115     }
116   }
117 
118   /**
119    * Some widgets fail to trigger some AccessibilityEvent's after actions,
120    * resulting in stale AccessibilityNodeInfo's. As a work-around, force to
121    * clear the AccessibilityNodeInfoCache.
122    */
clearAccessibilityNodeInfoCache()123   public void clearAccessibilityNodeInfoCache() {
124     Logs.call(this, "clearAccessibilityNodeInfoCache");
125     clearer.clearAccessibilityNodeInfoCache(this);
126   }
127 
128   public interface AccessibilityNodeInfoCacheClearer {
clearAccessibilityNodeInfoCache(UiAutomationDriver driver)129     void clearAccessibilityNodeInfoCache(UiAutomationDriver driver);
130   }
131 
132   /**
133    * Clears AccessibilityNodeInfoCache by turning screen off then on.
134    */
135   public static class ScreenOffAccessibilityNodeInfoCacheClearer implements
136       AccessibilityNodeInfoCacheClearer {
clearAccessibilityNodeInfoCache(UiAutomationDriver driver)137     public void clearAccessibilityNodeInfoCache(UiAutomationDriver driver) {
138       driver.getUiDevice().sleep();
139       driver.getUiDevice().wakeUp();
140     }
141   }
142 
143   /**
144    * Clears AccessibilityNodeInfoCache by exploiting an implementation detail of
145    * AccessibilityNodeInfoCache. This is a hack; use it at your own discretion.
146    */
147   public static class WindowStateAccessibilityNodeInfoCacheClearer implements
148       AccessibilityNodeInfoCacheClearer {
clearAccessibilityNodeInfoCache(UiAutomationDriver driver)149     public void clearAccessibilityNodeInfoCache(UiAutomationDriver driver) {
150       AccessibilityManager accessibilityManager =
151           (AccessibilityManager) driver.context.getInstrumentation().getTargetContext()
152               .getSystemService(Context.ACCESSIBILITY_SERVICE);
153       accessibilityManager.sendAccessibilityEvent(AccessibilityEvent
154           .obtain(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED));
155     }
156   }
157 
setAccessibilityNodeInfoCacheClearer(AccessibilityNodeInfoCacheClearer clearer)158   public void setAccessibilityNodeInfoCacheClearer(AccessibilityNodeInfoCacheClearer clearer) {
159     this.clearer = clearer;
160   }
161 
162   @Override
getUiDevice()163   public UiAutomationUiDevice getUiDevice() {
164     return uiDevice;
165   }
166 }
167