Merge "Fix treatment of suite() and class filters"
diff --git a/espresso/core/src/main/java/android/support/test/espresso/action/CloseKeyboardAction.java b/espresso/core/src/main/java/android/support/test/espresso/action/CloseKeyboardAction.java
index ce89a1d..2969f90 100644
--- a/espresso/core/src/main/java/android/support/test/espresso/action/CloseKeyboardAction.java
+++ b/espresso/core/src/main/java/android/support/test/espresso/action/CloseKeyboardAction.java
@@ -20,6 +20,8 @@
 import static com.google.common.collect.Iterables.getOnlyElement;
 import static org.hamcrest.Matchers.any;
 
+import android.support.test.espresso.Espresso;
+import android.support.test.espresso.IdlingResource;
 import android.support.test.runner.lifecycle.ActivityLifecycleMonitorRegistry;
 import android.support.test.runner.lifecycle.Stage;
 import android.support.test.espresso.PerformException;
@@ -30,6 +32,8 @@
 import android.app.Activity;
 import android.content.Context;
 import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
 import android.os.ResultReceiver;
 import android.util.Log;
 import android.view.View;
@@ -38,10 +42,7 @@
 import org.hamcrest.Matcher;
 
 import java.util.Collection;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
-import java.util.concurrent.atomic.AtomicInteger;
 
 /**
  * Closes soft keyboard.
@@ -79,46 +80,40 @@
 
   private void tryToCloseKeyboard(View view, UiController uiController) throws TimeoutException {
     InputMethodManager imm = (InputMethodManager) getRootActivity(uiController)
-        .getSystemService(Context.INPUT_METHOD_SERVICE);
-    final AtomicInteger atomicResultCode = new AtomicInteger();
-    final CountDownLatch latch = new CountDownLatch(1);
+            .getSystemService(Context.INPUT_METHOD_SERVICE);
 
-    ResultReceiver result = new ResultReceiver(null) {
-      @Override
-      protected void onReceiveResult(int resultCode, Bundle resultData) {
-        atomicResultCode.set(resultCode);
-        latch.countDown();
-      }
-    };
 
-    if (!imm.hideSoftInputFromWindow(view.getWindowToken(), 0, result)) {
-      Log.w(TAG, "Attempting to close soft keyboard, while it is not shown.");
-      return;
-    }
+    CloseKeyboardIdlingResource idlingResult = new CloseKeyboardIdlingResource(
+            new Handler(Looper.getMainLooper()));
 
+    Espresso.registerIdlingResources(idlingResult);
     try {
-      if (!latch.await(2, TimeUnit.SECONDS)) {
+
+      if (!imm.hideSoftInputFromWindow(view.getWindowToken(), 0, idlingResult)) {
+        Log.w(TAG, "Attempting to close soft keyboard, while it is not shown.");
+        return;
+      }
+      // set 2 second timeout
+      idlingResult.scheduleTimeout(2000);
+      uiController.loopMainThreadUntilIdle();
+      if (idlingResult.timedOut) {
         throw new TimeoutException("Wait on operation result timed out.");
       }
-    } catch (InterruptedException e) {
-      throw new PerformException.Builder()
-        .withActionDescription(this.getDescription())
-        .withViewDescription(HumanReadables.describe(view))
-        .withCause(new RuntimeException("Waiting for soft keyboard close result was interrupted."))
-        .build();
+    } finally {
+      Espresso.unregisterIdlingResources(idlingResult);
     }
 
-    if (atomicResultCode.get() != InputMethodManager.RESULT_UNCHANGED_HIDDEN
-        && atomicResultCode.get() != InputMethodManager.RESULT_HIDDEN) {
+    if (idlingResult.result != InputMethodManager.RESULT_UNCHANGED_HIDDEN
+            && idlingResult.result != InputMethodManager.RESULT_HIDDEN) {
       String error =
-          "Attempt to close the soft keyboard did not result in soft keyboard to be hidden."
-          + "resultCode = " + atomicResultCode.get();
+              "Attempt to close the soft keyboard did not result in soft keyboard to be hidden."
+                      + " resultCode = " + idlingResult.result;
       Log.e(TAG, error);
       throw new PerformException.Builder()
-        .withActionDescription(this.getDescription())
-        .withViewDescription(HumanReadables.describe(view))
-        .withCause(new RuntimeException(error))
-        .build();
+              .withActionDescription(this.getDescription())
+              .withViewDescription(HumanReadables.describe(view))
+              .withCause(new RuntimeException(error))
+              .build();
     }
   }
 
@@ -142,4 +137,86 @@
   public String getDescription() {
     return "close keyboard";
   }
+
+  /**
+   * {@link IdlingResource} to help Espresso synchronize keyboard closure animations
+   */
+  private static class CloseKeyboardIdlingResource extends ResultReceiver implements
+          IdlingResource {
+
+    // all set|read on main thread
+    private ResourceCallback resourceCallback;
+    // indicates that we've received a response from the inputmethod manager
+    private boolean receivedResult = false;
+    // the result IMM gave us (only valid if receivedResult is true)
+    private int result = -1;
+    // indicates we've timed out.
+    private boolean timedOut = false;
+    // the idle value we report to espresso.
+    private boolean idle = false;
+    private final Handler handler;
+
+    private CloseKeyboardIdlingResource(Handler h) {
+      super(h);
+      handler = h;
+    }
+
+    private void scheduleTimeout(long millis) {
+      handler.postDelayed(new Runnable() {
+        @Override
+        public void run() {
+          if (!receivedResult) {
+            timedOut = true;
+            if (null != resourceCallback) {
+              resourceCallback.onTransitionToIdle();
+            }
+          }
+        }
+      }, millis);
+    }
+
+    private void notifyEspresso(long millis) {
+      checkState(this.receivedResult);
+      handler.postDelayed(new Runnable() {
+        @Override
+        public void run() {
+          idle = true;
+          if (null != resourceCallback) {
+            resourceCallback.onTransitionToIdle();
+          }
+        }
+      }, millis);
+    }
+
+    @Override
+    protected void onReceiveResult(int resultCode, Bundle resultData) {
+      result = resultCode;
+      receivedResult = true;
+      // IMM responds to us first, before the messages are sent to the app that makes it draw
+      // over the region that was previously obscured by the keyboard. Therefore stay busy for a
+      // short period of time to allow for the redraw message to be sent to our app and processed.
+      notifyEspresso(300);
+    }
+
+    @Override
+    public String getName() {
+      return "CloseKeyboardIdlingResource";
+    }
+
+    @Override
+    public boolean isIdleNow() {
+      // Either IMM has responded to us or we've given up.
+      return idle || timedOut;
+    }
+
+    // handle the race where IMM responds before espresso even gives us our callback. TimedOut
+    // being true would be a big WTF, but we're idle too in that case.
+    @Override
+    public void registerIdleTransitionCallback(ResourceCallback callback) {
+      resourceCallback = callback;
+      if (idle || timedOut) {
+        resourceCallback.onTransitionToIdle();
+      }
+    }
+  }
 }
diff --git a/espresso/intents-tests/src/androidTest/java/android/support/test/espresso/intent/IntentsIntegrationTest.java b/espresso/intents-tests/src/androidTest/java/android/support/test/espresso/intent/IntentsIntegrationTest.java
index 153660c..69fc6f6 100644
--- a/espresso/intents-tests/src/androidTest/java/android/support/test/espresso/intent/IntentsIntegrationTest.java
+++ b/espresso/intents-tests/src/androidTest/java/android/support/test/espresso/intent/IntentsIntegrationTest.java
@@ -157,7 +157,7 @@
 
     // Testing Scheme "tel:xxx-xxx-xxxx"
     onView(withId(R.id.send_data_to_call_edit_text))
-        .perform(scrollTo(), typeText("123-345-6789"), closeSoftKeyboard());
+        .perform(closeSoftKeyboard(), scrollTo(), typeText("123-345-6789"), closeSoftKeyboard());
     onView(withId(R.id.send_to_call_button)).perform(scrollTo(), click());
     intended(allOf(
                 hasAction(Intent.ACTION_CALL),
@@ -254,15 +254,8 @@
   @Test
   @SuppressWarnings("unchecked")
   public void externalIntentWithType() {
-      String toPackage = "com.android.mms";
-      if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
-      // Starting with Android M the messaging package has changed.
-      toPackage = "com.google.android.apps.messaging";
-    }
     onView(withId(R.id.send_message_button)).perform(scrollTo(), click());
-    intended(allOf(hasAction(Intent.ACTION_SEND),
-        toPackage(toPackage),
-        hasType("text/plain")));
+    intended(allOf(hasAction(Intent.ACTION_SEND), hasType("text/plain")));
   }
 
   @Test
diff --git a/espresso/sample/src/main/res/layout/send_activity.xml b/espresso/sample/src/main/res/layout/send_activity.xml
index 925c14f..4a47ce1 100644
--- a/espresso/sample/src/main/res/layout/send_activity.xml
+++ b/espresso/sample/src/main/res/layout/send_activity.xml
@@ -29,7 +29,9 @@
     <LinearLayout
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:orientation="vertical" >
+        android:orientation="vertical"
+        android:focusable="true"
+        android:focusableInTouchMode="true">
 
         <TextView
             android:id="@+id/send_title"