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} */ 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 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 67 public InputInjector getInjector() { 68 return injector; 69 } 70 71 @Override 72 protected UiAutomationElement newRootElement() { 73 return context.newRootElement(getRootNode()); 74 } 75 76 @Override 77 protected UiAutomationElement newUiElement(AccessibilityNodeInfo rawElement, 78 UiAutomationElement parent) { 79 return new UiAutomationElement(context, rawElement, parent); 80 } 81 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 */ 123 public void clearAccessibilityNodeInfoCache() { 124 Logs.call(this, "clearAccessibilityNodeInfoCache"); 125 clearer.clearAccessibilityNodeInfoCache(this); 126 } 127 128 public interface AccessibilityNodeInfoCacheClearer { 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 { 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 { 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 158 public void setAccessibilityNodeInfoCacheClearer(AccessibilityNodeInfoCacheClearer clearer) { 159 this.clearer = clearer; 160 } 161 162 @Override 163 public UiAutomationUiDevice getUiDevice() { 164 return uiDevice; 165 } 166 } 167