Repository: appium/appium
Issue: Restart UIAutomator to find missing elements

Read the end of [this issue](https://github.com/appium/java-client/issues/74#issuecomment-61511540).

According to @ranbena restarting UIAutomator works %100 of the time to fix an issue where sometimes elements aren't returned by getPAgeSource(). It's definitely a bug in Android UIAutomator, but it seems like we can work around it by restarting the UIAutomator process.

Right now, restarting the process causes the Appium session to fail. What should we consider for fixing these missing elements?

Options I've thought of so far:
1) Make a "restart UIAutomator" function available to clients.
2) Restart UIAutomator whenever an element isn't found (or add it to some retry logic)
3) Create a "Setting" (from our settings api) which when enabled restarts UIAutomator when elements aren't found, and doesn't restart UIAutomator when disabled.

Any more ideas?
@jlipps @bootstraponline @sebv @imurchie @0x1mason @JessicaSachs 


Changed files:
lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/Source.java
lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/Find.java
lib/devices/android/bootstrap/src/com/android/uiautomator/common/ReflectionUtils.java

diff --git a/lib/devices/android/bootstrap/src/com/android/uiautomator/common/ReflectionUtils.java b/lib/devices/android/bootstrap/src/com/android/uiautomator/common/ReflectionUtils.java
index 29ab12461..e5c5b6cd6 100644
--- a/lib/devices/android/bootstrap/src/com/android/uiautomator/common/ReflectionUtils.java
+++ b/lib/devices/android/bootstrap/src/com/android/uiautomator/common/ReflectionUtils.java
@@ -7,6 +7,7 @@ import java.lang.reflect.Field;
 import java.lang.reflect.Method;
 
 import static io.appium.android.bootstrap.utils.API.API_18;
+import java.lang.reflect.InvocationTargetException;
 
 public class ReflectionUtils {
   private static Field enableField(final Class<?> clazz, final String field)
@@ -47,6 +48,32 @@ public class ReflectionUtils {
   public Object getBridge() {
     return bridge;
   }
+  
+  /**
+   * Clears the in-process Accessibility cache, removing any stale references.
+   * Because the AccessibilityInteractionClient singleton stores copies of AccessibilityNodeInfo
+   * instances, calls to public APIs such as `recycle` do not guarantee cached references get
+   * updated.  See the android.view.accessibility AIC and ANI source code for more information.
+   */
+  public static boolean clearAccessibilityCache() {
+    boolean success = false;
+    
+    try {
+      ReflectionUtils utils = new ReflectionUtils();
+      Class c = Class.forName("android.view.accessibility.AccessibilityInteractionClient");      
+      Method getInstance = utils.getMethod(c, "getInstance");
+      Object instance = getInstance.invoke(null);
+      Method clearCache = utils.getMethod(instance.getClass(), "clearCache");
+      clearCache.invoke(instance);
+      success = true;
+    } catch (Exception ex) {
+      // Expected: ClassNotFoundException, NoSuchMethodException, IllegalAccessException,
+      // InvocationTargetException, NoSuchFieldException
+      Logger.error("Failed to clear Accessibility Node cache. " + ex.getMessage());
+    }
+
+    return success;
+  }
 
   public Method getControllerMethod(final String name, final Class<?>... parameterTypes)
       throws NoSuchMethodException, SecurityException {
diff --git a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/Find.java b/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/Find.java
index d9b2f5bf6..d781eaae2 100644
--- a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/Find.java
+++ b/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/Find.java
@@ -1,7 +1,17 @@
 package io.appium.android.bootstrap.handler;
 
+import com.android.uiautomator.common.ReflectionUtils;
 import com.android.uiautomator.core.UiObjectNotFoundException;
 import com.android.uiautomator.core.UiSelector;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+import javax.xml.parsers.ParserConfigurationException;
+import java.util.ArrayList;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.regex.Pattern;
+
 import io.appium.android.bootstrap.*;
 import io.appium.android.bootstrap.exceptions.ElementNotFoundException;
 import io.appium.android.bootstrap.exceptions.InvalidSelectorException;
@@ -12,15 +22,6 @@ import io.appium.android.bootstrap.utils.ClassInstancePair;
 import io.appium.android.bootstrap.utils.ElementHelpers;
 import io.appium.android.bootstrap.utils.UiAutomatorParser;
 import io.appium.android.bootstrap.utils.XMLHierarchy;
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import javax.xml.parsers.ParserConfigurationException;
-import java.util.ArrayList;
-import java.util.Hashtable;
-import java.util.List;
-import java.util.regex.Pattern;
 
 import static io.appium.android.bootstrap.utils.API.API_18;
 
@@ -62,6 +63,24 @@ public class Find extends CommandHandler {
   @Override
   public AndroidCommandResult execute(final AndroidCommand command)
       throws JSONException {
+    return execute(command, false);
+  }
+  
+  /**
+   * execute implementation.
+   * 
+   * @see io.appium.android.bootstrap.handler.Find#execute(io.appium.android.
+   * bootstrap.AndroidCommand)
+   * 
+   * @param command The {@link AndroidCommand} used for this handler.
+   * 
+   * @param isRetry Is this invocation a second attempt?
+   * 
+   * @return {@link AndroidCommandResult}
+   * @throws JSONException 
+   */
+  private AndroidCommandResult execute(final AndroidCommand command, boolean isRetry)
+      throws JSONException {
     final Hashtable<String, Object> params = command.params();
 
     // only makes sense on a device
@@ -82,7 +101,6 @@ public class Find extends CommandHandler {
     try {
       Object result = null;
       List<UiSelector> selectors = getSelectors(strategy, text, multiple);
-
       if (!multiple) {
         for (final UiSelector sel : selectors) {
           try {
@@ -97,8 +115,7 @@ public class Find extends CommandHandler {
       } else {
         List<AndroidElement> foundElements = new ArrayList<AndroidElement>();
         for (final UiSelector sel : selectors) {
-          // With multiple selectors, we expect that some elements may not
-          // exist.
+          // With multiple selectors, we expect that some elements may not exist.
           try {
             Logger.debug("Using: " + sel.toString());
             List<AndroidElement> elementsFromSelector = fetchElements(sel, contextId);
@@ -112,10 +129,20 @@ public class Find extends CommandHandler {
         result = elementsToJSONArray(foundElements);
       }
 
-      // If there are no results, then return an error.
       if (result == null) {
-        return new AndroidCommandResult(WDStatus.NO_SUCH_ELEMENT,
-            "No element found");
+        if (!isRetry) {
+          Logger.debug("Failed to locate element. Clearing Accessibility cache and retrying.");
+          // some control updates fail to trigger AccessibilityEvents, resulting in stale AccessibilityNodeInfo
+          // instances. In these cases, UIAutomator will fail to locate visible elements. As a work-around,
+          // force clear the AccessibilityInteractionClient's cache and search again. This technique also
+          // appears to make Appium's searches conclude more quickly. See Appium issue #4200
+          // https://github.com/appium/appium/issues/4200
+          if (ReflectionUtils.clearAccessibilityCache()) {
+            return execute(command, true);
+          }
+        }
+        // If there are no results and we've already retried, return an error.
+        return new AndroidCommandResult(WDStatus.NO_SUCH_ELEMENT, "No element found");
       }
 
       return getSuccessResult(result);
@@ -132,6 +159,7 @@ public class Find extends CommandHandler {
     }
   }
 
+
   /**
    * Get the element from the {@link AndroidElementsHash} and return the element
    * id using JSON.
diff --git a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/Source.java b/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/Source.java
index 1097ab336..82c32eab9 100644
--- a/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/Source.java
+++ b/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/handler/Source.java
@@ -1,5 +1,6 @@
 package io.appium.android.bootstrap.handler;
 
+import com.android.uiautomator.common.ReflectionUtils;
 import io.appium.android.bootstrap.AndroidCommand;
 import io.appium.android.bootstrap.AndroidCommandResult;
 import io.appium.android.bootstrap.CommandHandler;
@@ -21,6 +22,7 @@ import java.io.StringWriter;
 public class Source extends CommandHandler {
   @Override
   public AndroidCommandResult execute(AndroidCommand command) throws JSONException {
+    ReflectionUtils.clearAccessibilityCache();
 
     Document doc = (Document) XMLHierarchy.getFormattedXMLDoc();