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"