Home | History | Annotate | Download | only in uiautomation
      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