summaryrefslogtreecommitdiff
path: root/espresso/espresso-lib/src/main/java/com/google/android/apps/common/testing/ui/espresso/base
diff options
context:
space:
mode:
Diffstat (limited to 'espresso/espresso-lib/src/main/java/com/google/android/apps/common/testing/ui/espresso/base')
-rw-r--r--espresso/espresso-lib/src/main/java/com/google/android/apps/common/testing/ui/espresso/base/AsyncTaskPoolMonitor.java208
-rw-r--r--espresso/espresso-lib/src/main/java/com/google/android/apps/common/testing/ui/espresso/base/BaseLayerModule.java167
-rw-r--r--espresso/espresso-lib/src/main/java/com/google/android/apps/common/testing/ui/espresso/base/CompatAsyncTask.java29
-rw-r--r--espresso/espresso-lib/src/main/java/com/google/android/apps/common/testing/ui/espresso/base/Default.java30
-rw-r--r--espresso/espresso-lib/src/main/java/com/google/android/apps/common/testing/ui/espresso/base/DefaultFailureHandler.java96
-rw-r--r--espresso/espresso-lib/src/main/java/com/google/android/apps/common/testing/ui/espresso/base/EventInjectionStrategy.java50
-rw-r--r--espresso/espresso-lib/src/main/java/com/google/android/apps/common/testing/ui/espresso/base/EventInjector.java99
-rw-r--r--espresso/espresso-lib/src/main/java/com/google/android/apps/common/testing/ui/espresso/base/IdlingResourceRegistry.java289
-rw-r--r--espresso/espresso-lib/src/main/java/com/google/android/apps/common/testing/ui/espresso/base/InputManagerEventInjectionStrategy.java162
-rw-r--r--espresso/espresso-lib/src/main/java/com/google/android/apps/common/testing/ui/espresso/base/LooperIdlingResource.java130
-rw-r--r--espresso/espresso-lib/src/main/java/com/google/android/apps/common/testing/ui/espresso/base/MainThread.java29
-rw-r--r--espresso/espresso-lib/src/main/java/com/google/android/apps/common/testing/ui/espresso/base/QueueInterrogator.java169
-rw-r--r--espresso/espresso-lib/src/main/java/com/google/android/apps/common/testing/ui/espresso/base/RootViewPicker.java217
-rw-r--r--espresso/espresso-lib/src/main/java/com/google/android/apps/common/testing/ui/espresso/base/RootsOracle.java167
-rw-r--r--espresso/espresso-lib/src/main/java/com/google/android/apps/common/testing/ui/espresso/base/SdkAsyncTask.java29
-rw-r--r--espresso/espresso-lib/src/main/java/com/google/android/apps/common/testing/ui/espresso/base/ThreadPoolExecutorExtractor.java154
-rw-r--r--espresso/espresso-lib/src/main/java/com/google/android/apps/common/testing/ui/espresso/base/UiControllerImpl.java535
-rw-r--r--espresso/espresso-lib/src/main/java/com/google/android/apps/common/testing/ui/espresso/base/ViewFinderImpl.java133
-rw-r--r--espresso/espresso-lib/src/main/java/com/google/android/apps/common/testing/ui/espresso/base/WindowManagerEventInjectionStrategy.java150
19 files changed, 0 insertions, 2843 deletions
diff --git a/espresso/espresso-lib/src/main/java/com/google/android/apps/common/testing/ui/espresso/base/AsyncTaskPoolMonitor.java b/espresso/espresso-lib/src/main/java/com/google/android/apps/common/testing/ui/espresso/base/AsyncTaskPoolMonitor.java
deleted file mode 100644
index 082c045..0000000
--- a/espresso/espresso-lib/src/main/java/com/google/android/apps/common/testing/ui/espresso/base/AsyncTaskPoolMonitor.java
+++ /dev/null
@@ -1,208 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.android.apps.common.testing.ui.espresso.base;
-
-import static com.google.common.base.Preconditions.checkNotNull;
-import static com.google.common.base.Preconditions.checkState;
-
-import java.util.concurrent.BrokenBarrierException;
-import java.util.concurrent.CyclicBarrier;
-import java.util.concurrent.ThreadPoolExecutor;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.concurrent.atomic.AtomicReference;
-
-/**
- * Provides a way to monitor AsyncTask's work queue to ensure that there is no work pending
- * or executing (and to allow notification of idleness).
- *
- * This class is based on the assumption that we can get at the ThreadPoolExecutor AsyncTask uses.
- * That is currently possible and easy in Froyo to JB. If it ever becomes impossible, as long as we
- * know the max # of executor threads the AsyncTask framework allows we can still use this
- * interface, just need a different implementation.
- */
-class AsyncTaskPoolMonitor {
- private final AtomicReference<IdleMonitor> monitor = new AtomicReference<IdleMonitor>(null);
- private final ThreadPoolExecutor pool;
- private final AtomicInteger activeBarrierChecks = new AtomicInteger(0);
-
- AsyncTaskPoolMonitor(ThreadPoolExecutor pool) {
- this.pool = checkNotNull(pool);
- }
-
- /**
- * Checks if the pool is idle at this moment.
- *
- * @return true if the pool is idle, false otherwise.
- */
- boolean isIdleNow() {
- if (!pool.getQueue().isEmpty()) {
- return false;
- } else {
- int activeCount = pool.getActiveCount();
- if (0 != activeCount) {
- if (monitor.get() == null) {
- // if there's no idle monitor scheduled and there are still barrier
- // checks running, they are about to exit, ignore them.
- activeCount = activeCount - activeBarrierChecks.get();
- }
- }
- return 0 == activeCount;
- }
- }
-
- /**
- * Notifies caller once the pool is idle.
- *
- * We check for idle-ness by submitting the max # of tasks the pool will take and blocking
- * the tasks until they are all executing. Then we know there are no other tasks _currently_
- * executing in the pool, we look back at the work queue to see if its backed up, if it is
- * we reenqueue ourselves and try again.
- *
- * Obviously this strategy will fail horribly if 2 parties are doing it at the same time,
- * we prevent recursion here the best we can.
- *
- * @param idleCallback called once the pool is idle.
- */
- void notifyWhenIdle(final Runnable idleCallback) {
- checkNotNull(idleCallback);
- IdleMonitor myMonitor = new IdleMonitor(idleCallback);
- checkState(monitor.compareAndSet(null, myMonitor), "cannot monitor for idle recursively!");
- myMonitor.monitorForIdle();
- }
-
- /**
- * Stops the idle monitoring mechanism if it is in place.
- *
- * Note: the callback may still be invoked after this method is called. The only thing
- * this method guarantees is that we will stop/cancel any blockign tasks we've placed
- * on the thread pool.
- */
- void cancelIdleMonitor() {
- IdleMonitor myMonitor = monitor.getAndSet(null);
- if (null != myMonitor) {
- myMonitor.poison();
- }
- }
-
- private class IdleMonitor {
- private final Runnable onIdle;
- private final AtomicInteger barrierGeneration = new AtomicInteger(0);
- private final CyclicBarrier barrier;
- // written by main, read by all.
- private volatile boolean poisoned;
-
- private IdleMonitor(final Runnable onIdle) {
- this.onIdle = checkNotNull(onIdle);
- this.barrier = new CyclicBarrier(pool.getCorePoolSize(),
- new Runnable() {
- @Override
- public void run() {
- if (pool.getQueue().isEmpty()) {
- // no one is behind us, so the queue is idle!
- monitor.compareAndSet(IdleMonitor.this, null);
- onIdle.run();
- } else {
- // work is waiting behind us, enqueue another block of tasks and
- // hopefully when they're all running, the queue will be empty.
- monitorForIdle();
- }
-
- }
- });
- }
-
- /**
- * Stops this monitor from using the thread pool's resources, it may still cause the
- * callback to be executed though.
- */
- private void poison() {
- poisoned = true;
- barrier.reset();
- }
-
- private void monitorForIdle() {
- if (poisoned) {
- return;
- }
-
- if (isIdleNow()) {
- monitor.compareAndSet(this, null);
- onIdle.run();
- } else {
- // Submit N tasks that will block until they are all running on the thread pool.
- // at this point we can check the pool's queue and verify that there are no new
- // tasks behind us and deem the queue idle.
-
- int poolSize = pool.getCorePoolSize();
- final BarrierRestarter restarter = new BarrierRestarter(barrier, barrierGeneration);
-
- for (int i = 0; i < poolSize; i++) {
- pool.execute(new Runnable() {
- @Override
- public void run() {
- while (!poisoned) {
- activeBarrierChecks.incrementAndGet();
- int myGeneration = barrierGeneration.get();
- try {
- barrier.await();
- return;
- } catch (InterruptedException ie) {
- // sorry - I cant let you interrupt me!
- restarter.restart(myGeneration);
- } catch (BrokenBarrierException bbe) {
- restarter.restart(myGeneration);
- } finally {
- activeBarrierChecks.decrementAndGet();
- }
- }
- }
- });
- }
- }
- }
- }
-
-
- private static class BarrierRestarter {
- private final CyclicBarrier barrier;
- private final AtomicInteger barrierGeneration;
- BarrierRestarter(CyclicBarrier barrier, AtomicInteger barrierGeneration) {
- this.barrier = barrier;
- this.barrierGeneration = barrierGeneration;
- }
-
- /**
- * restarts the barrier.
- *
- * After the calling this function it is guaranteed that barrier generation has been incremented
- * and the barrier can be awaited on again.
- *
- * @param fromGeneration the generation that encountered the breaking exception.
- */
- synchronized void restart(int fromGeneration) {
- // must be synchronized. T1 could pass the if check, be suspended before calling reset, T2
- // sails thru - and awaits on the barrier again before T1 has awoken and reset it.
- int nextGen = fromGeneration + 1;
- if (barrierGeneration.compareAndSet(fromGeneration, nextGen)) {
- // first time we've seen fromGeneration request a reset. lets reset the barrier.
- barrier.reset();
- } else {
- // some other thread has already reset the barrier - this request is a no op.
- }
- }
- }
-}
diff --git a/espresso/espresso-lib/src/main/java/com/google/android/apps/common/testing/ui/espresso/base/BaseLayerModule.java b/espresso/espresso-lib/src/main/java/com/google/android/apps/common/testing/ui/espresso/base/BaseLayerModule.java
deleted file mode 100644
index 6615b1d..0000000
--- a/espresso/espresso-lib/src/main/java/com/google/android/apps/common/testing/ui/espresso/base/BaseLayerModule.java
+++ /dev/null
@@ -1,167 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.android.apps.common.testing.ui.espresso.base;
-
-import com.google.android.apps.common.testing.testrunner.ActivityLifecycleMonitor;
-import com.google.android.apps.common.testing.testrunner.ActivityLifecycleMonitorRegistry;
-import com.google.android.apps.common.testing.testrunner.InstrumentationRegistry;
-import com.google.android.apps.common.testing.testrunner.inject.TargetContext;
-import com.google.android.apps.common.testing.ui.espresso.FailureHandler;
-import com.google.android.apps.common.testing.ui.espresso.Root;
-import com.google.android.apps.common.testing.ui.espresso.UiController;
-import com.google.common.base.Optional;
-
-import android.content.Context;
-import android.os.Build;
-import android.os.Handler;
-import android.os.Looper;
-
-import dagger.Module;
-import dagger.Provides;
-
-import java.util.List;
-import java.util.concurrent.Executor;
-import java.util.concurrent.ThreadPoolExecutor;
-import java.util.concurrent.atomic.AtomicReference;
-
-import javax.inject.Inject;
-import javax.inject.Singleton;
-
-/**
- * Dagger module for creating the implementation classes within the base package.
- */
-@Module(library = true, injects = {
- BaseLayerModule.FailureHandlerHolder.class, FailureHandler.class})
-public class BaseLayerModule {
-
- @Provides @Singleton
- public ActivityLifecycleMonitor provideLifecycleMonitor() {
- // TODO(user): replace with installation of AndroidInstrumentationModule once
- // proguard issues resolved.
- return ActivityLifecycleMonitorRegistry.getInstance();
- }
-
- @Provides @TargetContext
- public Context provideTargetContext() {
- // TODO(user): replace with installation of AndroidInstrumentationModule once
- // proguard issues resolved.
- return InstrumentationRegistry.getInstance().getTargetContext();
- }
-
- @Provides @Singleton
- public Looper provideMainLooper() {
- return Looper.getMainLooper();
- }
-
- @Provides
- public UiController provideUiController(UiControllerImpl uiControllerImpl) {
- return uiControllerImpl;
- }
-
- @Provides @Singleton @CompatAsyncTask
- public Optional<AsyncTaskPoolMonitor> provideCompatAsyncTaskMonitor(
- ThreadPoolExecutorExtractor extractor) {
- Optional<ThreadPoolExecutor> compatThreadPool = extractor.getCompatAsyncTaskThreadPool();
- if (compatThreadPool.isPresent()) {
- return Optional.of(new AsyncTaskPoolMonitor(compatThreadPool.get()));
- } else {
- return Optional.<AsyncTaskPoolMonitor>absent();
- }
- }
-
- @Provides @Singleton @MainThread
- public Executor provideMainThreadExecutor(Looper mainLooper) {
- final Handler handler = new Handler(mainLooper);
- return new Executor() {
- @Override
- public void execute(Runnable runnable) {
- handler.post(runnable);
- }
- };
- }
-
- @Provides @Singleton @SdkAsyncTask
- public AsyncTaskPoolMonitor provideSdkAsyncTaskMonitor(ThreadPoolExecutorExtractor extractor) {
- return new AsyncTaskPoolMonitor(extractor.getAsyncTaskThreadPool());
-
- }
-
- @Provides
- public List<Root> provideKnownRoots(RootsOracle rootsOracle) {
- // RootsOracle acts as a provider, but returning Providers is illegal, so delegate.
- return rootsOracle.get();
- }
-
- @Provides @Singleton
- public EventInjector provideEventInjector() {
- // On API 16 and above, android uses input manager to inject events. On API < 16,
- // they use Window Manager. So we need to create our InjectionStrategy depending on the api
- // level. Instrumentation does not check if the event presses went through by checking the
- // boolean return value of injectInputEvent, which is why we created this class to better
- // handle lost/dropped press events. Instrumentation cannot be used as a fallback strategy,
- // since this will be executed on the main thread.
- int sdkVersion = Build.VERSION.SDK_INT;
- EventInjectionStrategy injectionStrategy = null;
- if (sdkVersion >= 16) { // Use InputManager for API level 16 and up.
- InputManagerEventInjectionStrategy strategy = new InputManagerEventInjectionStrategy();
- strategy.initialize();
- injectionStrategy = strategy;
- } else if (sdkVersion >= 7) {
- // else Use WindowManager for API level 15 through 7.
- WindowManagerEventInjectionStrategy strategy = new WindowManagerEventInjectionStrategy();
- strategy.initialize();
- injectionStrategy = strategy;
- } else {
- throw new RuntimeException(
- "API Level 6 and below is not supported. You are running: " + sdkVersion);
- }
- return new EventInjector(injectionStrategy);
- }
-
- /**
- * Holder for AtomicReference<FailureHandler> which allows updating it at runtime.
- */
- @Singleton
- public static class FailureHandlerHolder {
- private final AtomicReference<FailureHandler> holder;
-
- @Inject
- public FailureHandlerHolder(@Default FailureHandler defaultHandler) {
- holder = new AtomicReference<FailureHandler>(defaultHandler);
- }
-
- public void update(FailureHandler handler) {
- holder.set(handler);
- }
-
- public FailureHandler get() {
- return holder.get();
- }
- }
-
- @Provides
- FailureHandler provideFailureHandler(FailureHandlerHolder holder) {
- return holder.get();
- }
-
- @Provides
- @Default
- FailureHandler provideFailureHander(DefaultFailureHandler impl) {
- return impl;
- }
-
-}
diff --git a/espresso/espresso-lib/src/main/java/com/google/android/apps/common/testing/ui/espresso/base/CompatAsyncTask.java b/espresso/espresso-lib/src/main/java/com/google/android/apps/common/testing/ui/espresso/base/CompatAsyncTask.java
deleted file mode 100644
index 11ec6ab..0000000
--- a/espresso/espresso-lib/src/main/java/com/google/android/apps/common/testing/ui/espresso/base/CompatAsyncTask.java
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.android.apps.common.testing.ui.espresso.base;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-import javax.inject.Qualifier;
-
-/**
- * Annotates a AsyncTaskMonitor as monitoring the CompatAsyncTask pool
- */
-@Qualifier
-@Retention(RetentionPolicy.RUNTIME)
-@interface CompatAsyncTask { }
diff --git a/espresso/espresso-lib/src/main/java/com/google/android/apps/common/testing/ui/espresso/base/Default.java b/espresso/espresso-lib/src/main/java/com/google/android/apps/common/testing/ui/espresso/base/Default.java
deleted file mode 100644
index b54440d..0000000
--- a/espresso/espresso-lib/src/main/java/com/google/android/apps/common/testing/ui/espresso/base/Default.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.android.apps.common.testing.ui.espresso.base;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-import javax.inject.Qualifier;
-
-/**
- * Annotates a default provider.
- */
-@Qualifier
-@Retention(RetentionPolicy.RUNTIME)
-public @interface Default {
-}
diff --git a/espresso/espresso-lib/src/main/java/com/google/android/apps/common/testing/ui/espresso/base/DefaultFailureHandler.java b/espresso/espresso-lib/src/main/java/com/google/android/apps/common/testing/ui/espresso/base/DefaultFailureHandler.java
deleted file mode 100644
index b1e43da..0000000
--- a/espresso/espresso-lib/src/main/java/com/google/android/apps/common/testing/ui/espresso/base/DefaultFailureHandler.java
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.android.apps.common.testing.ui.espresso.base;
-
-import static com.google.common.base.Preconditions.checkNotNull;
-import static com.google.common.base.Throwables.propagate;
-
-import com.google.android.apps.common.testing.testrunner.inject.TargetContext;
-import com.google.android.apps.common.testing.ui.espresso.EspressoException;
-import com.google.android.apps.common.testing.ui.espresso.FailureHandler;
-import com.google.android.apps.common.testing.ui.espresso.PerformException;
-
-import android.content.Context;
-import android.view.View;
-
-import junit.framework.AssertionFailedError;
-
-import org.hamcrest.Matcher;
-
-import java.util.concurrent.atomic.AtomicInteger;
-
-import javax.inject.Inject;
-
-/**
- * Espresso's default {@link FailureHandler}. If this does not fit your needs, feel free to provide
- * your own implementation via Espresso.setFailureHandler(FailureHandler).
- */
-public final class DefaultFailureHandler implements FailureHandler {
-
- private static final AtomicInteger failureCount = new AtomicInteger(0);
- private final Context appContext;
-
- @Inject
- public DefaultFailureHandler(@TargetContext Context appContext) {
- this.appContext = checkNotNull(appContext);
- }
-
- @Override
- public void handle(Throwable error, Matcher<View> viewMatcher) {
- if (error instanceof EspressoException || error instanceof AssertionFailedError
- || error instanceof AssertionError) {
- throw propagate(getUserFriendlyError(error, viewMatcher));
- } else {
- throw propagate(error);
- }
- }
-
- /**
- * When the error is coming from espresso, it is more user friendly to:
- * 1. propagate assertions as assertions
- * 2. swap the stack trace of the error to that of current thread (which will show
- * directly where the actual problem is)
- */
- private Throwable getUserFriendlyError(Throwable error, Matcher<View> viewMatcher) {
- if (error instanceof PerformException) {
- // Re-throw the exception with the viewMatcher (used to locate the view) as the view
- // description (makes the error more readable). The reason we do this here: not all creators
- // of PerformException have access to the viewMatcher.
- throw new PerformException.Builder()
- .from((PerformException) error)
- .withViewDescription(viewMatcher.toString())
- .build();
- }
-
- if (error instanceof AssertionError) {
- // reports Failure instead of Error.
- // assertThat(...) throws an AssertionFailedError.
- error = new AssertionFailedWithCauseError(error.getMessage(), error);
- }
-
- error.setStackTrace(Thread.currentThread().getStackTrace());
- return error;
- }
-
- private static final class AssertionFailedWithCauseError extends AssertionFailedError {
- /* junit hides the cause constructor. */
- public AssertionFailedWithCauseError(String message, Throwable cause) {
- super(message);
- initCause(cause);
- }
- }
-}
diff --git a/espresso/espresso-lib/src/main/java/com/google/android/apps/common/testing/ui/espresso/base/EventInjectionStrategy.java b/espresso/espresso-lib/src/main/java/com/google/android/apps/common/testing/ui/espresso/base/EventInjectionStrategy.java
deleted file mode 100644
index 3378197..0000000
--- a/espresso/espresso-lib/src/main/java/com/google/android/apps/common/testing/ui/espresso/base/EventInjectionStrategy.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.android.apps.common.testing.ui.espresso.base;
-
-import com.google.android.apps.common.testing.ui.espresso.InjectEventSecurityException;
-
-import android.view.KeyEvent;
-import android.view.MotionEvent;
-
-/**
- * Injects Events into the application under test. Implementors should expect to be called
- * from the UI thread and are responsible for ensuring the event gets delivered or indicating that
- * it could not be delivered.
- */
-interface EventInjectionStrategy {
- /**
- * Injects the given {@link KeyEvent} into the android system.
- *
- * @param keyEvent The event to inject
- * @return {@code true} if the input was inject successfully, {@code false} otherwise.
- * @throws InjectEventSecurityException if the MotionEvent would be delivered to an area of the
- * screen that is not owned by the application under test.
- */
- boolean injectKeyEvent(KeyEvent keyEvent) throws InjectEventSecurityException;
-
- /**
- * Injects the given {@link MotionEvent} into the android system.
- *
- * @param motionEvent The event to inject
- * @return {@code true} if the input was inject successfully, {@code false} otherwise.
- * @throws InjectEventSecurityException if the MotionEvent would be delivered to an area of the
- * screen that is not owned by the application under test.
- */
- boolean injectMotionEvent(MotionEvent motionEvent) throws InjectEventSecurityException;
-
-}
diff --git a/espresso/espresso-lib/src/main/java/com/google/android/apps/common/testing/ui/espresso/base/EventInjector.java b/espresso/espresso-lib/src/main/java/com/google/android/apps/common/testing/ui/espresso/base/EventInjector.java
deleted file mode 100644
index 0728331..0000000
--- a/espresso/espresso-lib/src/main/java/com/google/android/apps/common/testing/ui/espresso/base/EventInjector.java
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.android.apps.common.testing.ui.espresso.base;
-
-import static com.google.common.base.Preconditions.checkNotNull;
-
-import com.google.android.apps.common.testing.ui.espresso.InjectEventSecurityException;
-
-import android.os.Build;
-import android.os.SystemClock;
-import android.util.Log;
-import android.view.KeyEvent;
-import android.view.MotionEvent;
-
-/**
- * Responsible for selecting the proper strategy for injecting MotionEvents to the application under
- * test.
- */
-final class EventInjector {
- private static final String TAG = EventInjector.class.getSimpleName();
- private final EventInjectionStrategy injectionStrategy;
-
- EventInjector(EventInjectionStrategy injectionStrategy) {
- this.injectionStrategy = checkNotNull(injectionStrategy);
- }
-
- boolean injectKeyEvent(KeyEvent event) throws InjectEventSecurityException {
- long downTime = event.getDownTime();
- long eventTime = event.getEventTime();
- int action = event.getAction();
- int code = event.getKeyCode();
- int repeatCount = event.getRepeatCount();
- int metaState = event.getMetaState();
- int deviceId = event.getDeviceId();
- int scancode = event.getScanCode();
- int flags = event.getFlags();
-
- if (eventTime == 0) {
- eventTime = SystemClock.uptimeMillis();
- }
-
- if (downTime == 0) {
- downTime = eventTime;
- }
-
- // API < 9 does not have constructor with source (nor has source field).
- KeyEvent newEvent;
- if (Build.VERSION.SDK_INT < 9) {
- newEvent = new KeyEvent(downTime,
- eventTime,
- action,
- code,
- repeatCount,
- metaState,
- deviceId,
- scancode,
- flags | KeyEvent.FLAG_FROM_SYSTEM);
- } else {
- int source = event.getSource();
- newEvent = new KeyEvent(downTime,
- eventTime,
- action,
- code,
- repeatCount,
- metaState,
- deviceId,
- scancode,
- flags | KeyEvent.FLAG_FROM_SYSTEM,
- source);
- }
-
- Log.v(
- "ESP_TRACE",
- String.format(
- "%s:Injecting event for character (%c) with key code (%s) downtime: (%s)", TAG,
- newEvent.getUnicodeChar(), newEvent.getKeyCode(), newEvent.getDownTime()));
-
- return injectionStrategy.injectKeyEvent(newEvent);
- }
-
- boolean injectMotionEvent(MotionEvent event) throws InjectEventSecurityException {
- return injectionStrategy.injectMotionEvent(event);
- }
-
-}
diff --git a/espresso/espresso-lib/src/main/java/com/google/android/apps/common/testing/ui/espresso/base/IdlingResourceRegistry.java b/espresso/espresso-lib/src/main/java/com/google/android/apps/common/testing/ui/espresso/base/IdlingResourceRegistry.java
deleted file mode 100644
index e390f0f..0000000
--- a/espresso/espresso-lib/src/main/java/com/google/android/apps/common/testing/ui/espresso/base/IdlingResourceRegistry.java
+++ /dev/null
@@ -1,289 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.android.apps.common.testing.ui.espresso.base;
-
-import static com.google.common.base.Preconditions.checkArgument;
-import static com.google.common.base.Preconditions.checkNotNull;
-import static com.google.common.base.Preconditions.checkState;
-
-import com.google.android.apps.common.testing.ui.espresso.IdlingPolicies;
-import com.google.android.apps.common.testing.ui.espresso.IdlingPolicy;
-import com.google.android.apps.common.testing.ui.espresso.IdlingResource;
-import com.google.android.apps.common.testing.ui.espresso.IdlingResource.ResourceCallback;
-import com.google.common.collect.Lists;
-
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.util.Log;
-
-import java.util.BitSet;
-import java.util.List;
-
-import javax.inject.Inject;
-import javax.inject.Singleton;
-
-/**
- * Keeps track of user-registered {@link IdlingResource}s.
- */
-@Singleton
-public final class IdlingResourceRegistry {
- private static final String TAG = IdlingResourceRegistry.class.getSimpleName();
-
- private static final int DYNAMIC_RESOURCE_HAS_IDLED = 1;
- private static final int TIMEOUT_OCCURRED = 2;
- private static final int IDLE_WARNING_REACHED = 3;
- private static final int POSSIBLE_RACE_CONDITION_DETECTED = 4;
- private static final Object TIMEOUT_MESSAGE_TAG = new Object();
-
- private static final IdleNotificationCallback NO_OP_CALLBACK = new IdleNotificationCallback() {
-
- @Override
- public void allResourcesIdle() {}
-
- @Override
- public void resourcesStillBusyWarning(List<String> busys) {}
-
- @Override
- public void resourcesHaveTimedOut(List<String> busys) {}
- };
-
- // resources and idleState should only be accessed on main thread
- private final List<IdlingResource> resources = Lists.newArrayList();
- // idleState.get(i) == true indicates resources.get(i) is idle, false indicates it's busy
- private final BitSet idleState = new BitSet();
- private final Looper looper;
- private final Handler handler;
- private final Dispatcher dispatcher;
- private IdleNotificationCallback idleNotificationCallback = NO_OP_CALLBACK;
-
- @Inject
- public IdlingResourceRegistry(Looper looper) {
- this.looper = looper;
- this.dispatcher = new Dispatcher();
- this.handler = new Handler(looper, dispatcher);
- }
-
- /**
- * Registers the given resource.
- */
- public void register(final IdlingResource resource) {
- checkNotNull(resource);
- if (Looper.myLooper() != looper) {
- handler.post(new Runnable() {
- @Override
- public void run() {
- register(resource);
- }
- });
- } else {
- for (IdlingResource oldResource : resources) {
- if (resource.getName().equals(oldResource.getName())) {
- // This does not throw an error to avoid leaving tests that register resource in test
- // setup in an undeterministic state (we cannot assume that everyone clears vm state
- // between each test run)
- Log.e(TAG, String.format("Attempted to register resource with same names:" +
- " %s. R1: %s R2: %s.\nDuplicate resource registration will be ignored.",
- resource.getName(), resource, oldResource));
- return;
- }
- }
- resources.add(resource);
- final int position = resources.size() - 1;
- registerToIdleCallback(resource, position);
- idleState.set(position, resource.isIdleNow());
- }
- }
-
- public void registerLooper(Looper looper, boolean considerWaitIdle) {
- checkNotNull(looper);
- checkArgument(Looper.getMainLooper() != looper, "Not intended for use with main looper!");
- register(new LooperIdlingResource(looper, considerWaitIdle));
- }
-
- private void registerToIdleCallback(IdlingResource resource, final int position) {
- resource.registerIdleTransitionCallback(new ResourceCallback() {
- @Override
- public void onTransitionToIdle() {
- Message m = handler.obtainMessage(DYNAMIC_RESOURCE_HAS_IDLED);
- m.arg1 = position;
- handler.sendMessage(m);
- }
- });
- }
-
- boolean allResourcesAreIdle() {
- checkState(Looper.myLooper() == looper);
- for (int i = idleState.nextSetBit(0); i >= 0 && i < resources.size();
- i = idleState.nextSetBit(i + 1)) {
- idleState.set(i, resources.get(i).isIdleNow());
- }
- return idleState.cardinality() == resources.size();
- }
-
- interface IdleNotificationCallback {
- public void allResourcesIdle();
-
- public void resourcesStillBusyWarning(List<String> busyResourceNames);
-
- public void resourcesHaveTimedOut(List<String> busyResourceNames);
- }
-
- void notifyWhenAllResourcesAreIdle(IdleNotificationCallback callback) {
- checkNotNull(callback);
- checkState(Looper.myLooper() == looper);
- checkState(idleNotificationCallback == NO_OP_CALLBACK, "Callback has already been registered.");
- if (allResourcesAreIdle()) {
- callback.allResourcesIdle();
- } else {
- idleNotificationCallback = callback;
- scheduleTimeoutMessages();
- }
- }
-
- void cancelIdleMonitor() {
- dispatcher.deregister();
- }
-
- private void scheduleTimeoutMessages() {
- IdlingPolicy warning = IdlingPolicies.getDynamicIdlingResourceWarningPolicy();
- Message timeoutWarning = handler.obtainMessage(IDLE_WARNING_REACHED, TIMEOUT_MESSAGE_TAG);
- handler.sendMessageDelayed(timeoutWarning, warning.getIdleTimeoutUnit().toMillis(
- warning.getIdleTimeout()));
- Message timeoutError = handler.obtainMessage(TIMEOUT_OCCURRED, TIMEOUT_MESSAGE_TAG);
- IdlingPolicy error = IdlingPolicies.getDynamicIdlingResourceErrorPolicy();
-
- handler.sendMessageDelayed(timeoutError, error.getIdleTimeoutUnit().toMillis(
- error.getIdleTimeout()));
- }
-
- private List<String> getBusyResources() {
- List<String> busyResourceNames = Lists.newArrayList();
- List<Integer> racyResources = Lists.newArrayList();
-
- for (int i = 0; i < resources.size(); i++) {
- IdlingResource resource = resources.get(i);
- if (!idleState.get(i)) {
- if (resource.isIdleNow()) {
- // We have not been notified of a BUSY -> IDLE transition, but the resource is telling us
- // its that its idle. Either it's a race condition or is this resource buggy.
- racyResources.add(i);
- } else {
- busyResourceNames.add(resource.getName());
- }
- }
- }
-
- if (!racyResources.isEmpty()) {
- Message raceBuster = handler.obtainMessage(POSSIBLE_RACE_CONDITION_DETECTED,
- TIMEOUT_MESSAGE_TAG);
- raceBuster.obj = racyResources;
- handler.sendMessage(raceBuster);
- return null;
- } else {
- return busyResourceNames;
- }
- }
-
-
- private class Dispatcher implements Handler.Callback {
- @Override
- public boolean handleMessage(Message m) {
- switch (m.what) {
- case DYNAMIC_RESOURCE_HAS_IDLED:
- handleResourceIdled(m);
- break;
- case IDLE_WARNING_REACHED:
- handleTimeoutWarning();
- break;
- case TIMEOUT_OCCURRED:
- handleTimeout();
- break;
- case POSSIBLE_RACE_CONDITION_DETECTED:
- handleRaceCondition(m);
- break;
- default:
- Log.w(TAG, "Unknown message type: " + m);
- return false;
- }
- return true;
- }
-
- private void handleResourceIdled(Message m) {
- idleState.set(m.arg1, true);
- if (idleState.cardinality() == resources.size()) {
- try {
- idleNotificationCallback.allResourcesIdle();
- } finally {
- deregister();
- }
- }
- }
-
- private void handleTimeoutWarning() {
- List<String> busyResources = getBusyResources();
- if (busyResources == null) {
- // null indicates that there is either a race or a programming error
- // a race detector message has been inserted into the q.
- // reinsert the idle_warning_reached message into the q directly after it
- // so we generate warnings if the system is still sane.
- handler.sendMessage(handler.obtainMessage(IDLE_WARNING_REACHED, TIMEOUT_MESSAGE_TAG));
- } else {
- IdlingPolicy warning = IdlingPolicies.getDynamicIdlingResourceWarningPolicy();
- idleNotificationCallback.resourcesStillBusyWarning(busyResources);
- handler.sendMessageDelayed(
- handler.obtainMessage(IDLE_WARNING_REACHED, TIMEOUT_MESSAGE_TAG),
- warning.getIdleTimeoutUnit().toMillis(warning.getIdleTimeout()));
- }
- }
-
- private void handleTimeout() {
- List<String> busyResources = getBusyResources();
- if (busyResources == null) {
- // detected a possible race... we've enqueued a race busting message
- // so either that'll resolve the race or kill the app because it's buggy.
- // if the race resolves, we need to timeout properly.
- handler.sendMessage(handler.obtainMessage(TIMEOUT_OCCURRED, TIMEOUT_MESSAGE_TAG));
- } else {
- try {
- idleNotificationCallback.resourcesHaveTimedOut(busyResources);
- } finally {
- deregister();
- }
- }
- }
-
- @SuppressWarnings("unchecked")
- private void handleRaceCondition(Message m) {
- for (Integer i : (List<Integer>) m.obj) {
- if (idleState.get(i)) {
- // it was a race... i is now idle, everything is fine...
- } else {
- throw new IllegalStateException(String.format(
- "Resource %s isIdleNow() is returning true, but a message indicating that the "
- + "resource has transitioned from busy to idle was never sent.",
- resources.get(i).getName()));
- }
- }
- }
-
- private void deregister() {
- handler.removeCallbacksAndMessages(TIMEOUT_MESSAGE_TAG);
- idleNotificationCallback = NO_OP_CALLBACK;
- }
- }
-}
diff --git a/espresso/espresso-lib/src/main/java/com/google/android/apps/common/testing/ui/espresso/base/InputManagerEventInjectionStrategy.java b/espresso/espresso-lib/src/main/java/com/google/android/apps/common/testing/ui/espresso/base/InputManagerEventInjectionStrategy.java
deleted file mode 100644
index d324795..0000000
--- a/espresso/espresso-lib/src/main/java/com/google/android/apps/common/testing/ui/espresso/base/InputManagerEventInjectionStrategy.java
+++ /dev/null
@@ -1,162 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.android.apps.common.testing.ui.espresso.base;
-
-import static com.google.common.base.Preconditions.checkState;
-import static com.google.common.base.Throwables.propagate;
-
-import com.google.android.apps.common.testing.ui.espresso.InjectEventSecurityException;
-
-import android.os.Build;
-import android.util.Log;
-import android.view.InputDevice;
-import android.view.InputEvent;
-import android.view.KeyEvent;
-import android.view.MotionEvent;
-
-import java.lang.reflect.Field;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-
-/**
- * An {@link EventInjectionStrategy} that uses the input manager to inject Events.
- * This strategy supports API level 16 and above.
- */
-final class InputManagerEventInjectionStrategy implements EventInjectionStrategy {
- private static final String TAG = InputManagerEventInjectionStrategy.class.getSimpleName();
-
- // Used in reflection
- private boolean initComplete;
- private Method injectInputEventMethod;
- private Method setSourceMotionMethod;
- private Object instanceInputManagerObject;
- private int motionEventMode;
- private int keyEventMode;
-
- InputManagerEventInjectionStrategy() {
- checkState(Build.VERSION.SDK_INT >= 16, "Unsupported API level.");
- }
-
- void initialize() {
- if (initComplete) {
- return;
- }
-
- try {
- Log.d(TAG, "Creating injection strategy with input manager.");
-
- // Get the InputputManager class object and initialize if necessary.
- Class<?> inputManagerClassObject = Class.forName("android.hardware.input.InputManager");
- Method getInstanceMethod = inputManagerClassObject.getDeclaredMethod("getInstance");
- getInstanceMethod.setAccessible(true);
-
- instanceInputManagerObject = getInstanceMethod.invoke(inputManagerClassObject);
-
- injectInputEventMethod = instanceInputManagerObject.getClass()
- .getDeclaredMethod("injectInputEvent", InputEvent.class, Integer.TYPE);
- injectInputEventMethod.setAccessible(true);
-
- // Setting event mode to INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH to ensure
- // that we've dispatched the event and any side effects its had on the view hierarchy
- // have occurred.
- Field motionEventModeField =
- inputManagerClassObject.getField("INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH");
- motionEventModeField.setAccessible(true);
- motionEventMode = motionEventModeField.getInt(inputManagerClassObject);
-
- Field keyEventModeField =
- inputManagerClassObject.getField("INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH");
- keyEventModeField.setAccessible(true);
- keyEventMode = keyEventModeField.getInt(inputManagerClassObject);
-
- setSourceMotionMethod = MotionEvent.class.getDeclaredMethod("setSource", Integer.TYPE);
- InputEvent.class.getDeclaredMethod("getSequenceNumber");
- initComplete = true;
- } catch (ClassNotFoundException e) {
- propagate(e);
- } catch (IllegalAccessException e) {
- propagate(e);
- } catch (IllegalArgumentException e) {
- propagate(e);
- } catch (InvocationTargetException e) {
- propagate(e);
- } catch (NoSuchMethodException e) {
- propagate(e);
- } catch (SecurityException e) {
- propagate(e);
- } catch (NoSuchFieldException e) {
- propagate(e);
- }
- }
-
- @Override
- public boolean injectKeyEvent(KeyEvent keyEvent) throws InjectEventSecurityException {
- try {
- return (Boolean) injectInputEventMethod.invoke(instanceInputManagerObject,
- keyEvent, keyEventMode);
- } catch (IllegalAccessException e) {
- propagate(e);
- } catch (IllegalArgumentException e) {
- propagate(e);
- } catch (InvocationTargetException e) {
- Throwable cause = e.getCause();
- if (cause instanceof SecurityException) {
- throw new InjectEventSecurityException(cause);
- }
- propagate(e);
- } catch (SecurityException e) {
- throw new InjectEventSecurityException(e);
- }
- return false;
- }
-
- @Override
- public boolean injectMotionEvent(MotionEvent motionEvent) throws InjectEventSecurityException {
- try {
- // Need to set the event source to touch screen, otherwise the input can be ignored even
- // though injecting it would be successful.
- // TODO(user): proper handling of events from a trackball (SOURCE_TRACKBALL) and joystick.
- if ((motionEvent.getSource() & InputDevice.SOURCE_CLASS_POINTER) == 0
- && !isFromTouchpadInGlassDevice(motionEvent)) {
- // Need to do runtime invocation of setSource because it was not added until 2.3_r1.
- setSourceMotionMethod.invoke(motionEvent, InputDevice.SOURCE_TOUCHSCREEN);
- }
- return (Boolean) injectInputEventMethod.invoke(instanceInputManagerObject,
- motionEvent, motionEventMode);
- } catch (IllegalAccessException e) {
- propagate(e);
- } catch (IllegalArgumentException e) {
- propagate(e);
- } catch (InvocationTargetException e) {
- Throwable cause = e.getCause();
- if (cause instanceof SecurityException) {
- throw new InjectEventSecurityException(cause);
- }
- propagate(e);
- } catch (SecurityException e) {
- throw new InjectEventSecurityException(e);
- }
- return false;
- }
-
- // We'd like to inject non-pointer events sourced from touchpad in Glass.
- private static boolean isFromTouchpadInGlassDevice(MotionEvent motionEvent) {
- return (Build.DEVICE.contains("glass")
- || Build.DEVICE.contains("Glass") || Build.DEVICE.contains("wingman"))
- && ((motionEvent.getSource() & InputDevice.SOURCE_TOUCHPAD) != 0);
- }
-}
diff --git a/espresso/espresso-lib/src/main/java/com/google/android/apps/common/testing/ui/espresso/base/LooperIdlingResource.java b/espresso/espresso-lib/src/main/java/com/google/android/apps/common/testing/ui/espresso/base/LooperIdlingResource.java
deleted file mode 100644
index b75fd36..0000000
--- a/espresso/espresso-lib/src/main/java/com/google/android/apps/common/testing/ui/espresso/base/LooperIdlingResource.java
+++ /dev/null
@@ -1,130 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.android.apps.common.testing.ui.espresso.base;
-
-import static com.google.common.base.Preconditions.checkNotNull;
-import static com.google.common.base.Preconditions.checkState;
-
-import com.google.android.apps.common.testing.ui.espresso.IdlingResource;
-import com.google.android.apps.common.testing.ui.espresso.IdlingResource.ResourceCallback;
-import com.google.android.apps.common.testing.ui.espresso.base.QueueInterrogator.QueueState;
-
-import android.os.Handler;
-import android.os.Looper;
-import android.os.MessageQueue.IdleHandler;
-
-/**
- * An Idling Resource Adapter for Loopers.
- */
-final class LooperIdlingResource implements IdlingResource {
-
- private static final String TAG = "LooperIdleResource";
-
- private final boolean considerWaitIdle;
- private final Looper monitoredLooper;
- private final Handler monitoredHandler;
-
- private ResourceCallback resourceCallback;
-
- LooperIdlingResource(Looper monitoredLooper, boolean considerWaitIdle) {
- this.monitoredLooper = checkNotNull(monitoredLooper);
- this.monitoredHandler = new Handler(monitoredLooper);
- this.considerWaitIdle = considerWaitIdle;
- checkState(Looper.getMainLooper() != monitoredLooper, "Not for use with main looper.");
- }
-
- // Only assigned and read from the main loop.
- private QueueInterrogator queueInterrogator;
-
- @Override
- public String getName() {
- return monitoredLooper.getThread().getName();
- }
-
- @Override
- public boolean isIdleNow() {
- // on main thread here.
- QueueState state = queueInterrogator.determineQueueState();
- boolean idle = state == QueueState.EMPTY || state == QueueState.TASK_DUE_LONG;
- boolean idleWait = considerWaitIdle
- && monitoredLooper.getThread().getState() == Thread.State.WAITING;
- if (idleWait) {
- if (resourceCallback != null) {
- resourceCallback.onTransitionToIdle();
- }
- }
- return idle || idleWait;
- }
-
- @Override
- public void registerIdleTransitionCallback(ResourceCallback resourceCallback) {
- this.resourceCallback = resourceCallback;
- // on main thread here.
- queueInterrogator = new QueueInterrogator(monitoredLooper);
-
- // must load idle handlers from monitored looper thread.
- IdleHandler idleHandler = new ResourceCallbackIdleHandler(resourceCallback, queueInterrogator,
- monitoredHandler);
-
- checkState(monitoredHandler.postAtFrontOfQueue(new Initializer(idleHandler)),
- "Monitored looper exiting.");
- }
-
- private static class ResourceCallbackIdleHandler implements IdleHandler {
- private final ResourceCallback resourceCallback;
- private final QueueInterrogator myInterrogator;
- private final Handler myHandler;
-
- ResourceCallbackIdleHandler(ResourceCallback resourceCallback,
- QueueInterrogator myInterrogator, Handler myHandler) {
- this.resourceCallback = checkNotNull(resourceCallback);
- this.myInterrogator = checkNotNull(myInterrogator);
- this.myHandler = checkNotNull(myHandler);
- }
-
- @Override
- public boolean queueIdle() {
- // invoked on the monitored looper thread.
- QueueState queueState = myInterrogator.determineQueueState();
- if (queueState == QueueState.EMPTY || queueState == QueueState.TASK_DUE_LONG) {
- // no block and no task coming 'shortly'.
- resourceCallback.onTransitionToIdle();
- } else if (queueState == QueueState.BARRIER) {
- // send a sentinal message that'll cause us to queueIdle again once the
- // block is lifted.
- myHandler.sendEmptyMessage(-1);
- }
-
- return true;
- }
- }
-
- private static class Initializer implements Runnable {
- private final IdleHandler myIdleHandler;
-
- Initializer(IdleHandler myIdleHandler) {
- this.myIdleHandler = checkNotNull(myIdleHandler);
- }
-
- @Override
- public void run() {
- // on monitored looper thread.
- Looper.myQueue().addIdleHandler(myIdleHandler);
- }
- }
-
-}
diff --git a/espresso/espresso-lib/src/main/java/com/google/android/apps/common/testing/ui/espresso/base/MainThread.java b/espresso/espresso-lib/src/main/java/com/google/android/apps/common/testing/ui/espresso/base/MainThread.java
deleted file mode 100644
index c431f48..0000000
--- a/espresso/espresso-lib/src/main/java/com/google/android/apps/common/testing/ui/espresso/base/MainThread.java
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.android.apps.common.testing.ui.espresso.base;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-import javax.inject.Qualifier;
-
-/**
- * Annotates an Executor that executes tasks on the main thread
- */
-@Qualifier
-@Retention(RetentionPolicy.RUNTIME)
-public @interface MainThread { }
diff --git a/espresso/espresso-lib/src/main/java/com/google/android/apps/common/testing/ui/espresso/base/QueueInterrogator.java b/espresso/espresso-lib/src/main/java/com/google/android/apps/common/testing/ui/espresso/base/QueueInterrogator.java
deleted file mode 100644
index cd63eb8..0000000
--- a/espresso/espresso-lib/src/main/java/com/google/android/apps/common/testing/ui/espresso/base/QueueInterrogator.java
+++ /dev/null
@@ -1,169 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.android.apps.common.testing.ui.espresso.base;
-
-import static com.google.common.base.Preconditions.checkNotNull;
-import static com.google.common.base.Preconditions.checkState;
-import static com.google.common.base.Throwables.propagate;
-
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.os.MessageQueue;
-import android.os.SystemClock;
-import android.util.Log;
-
-import java.lang.reflect.Field;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.util.concurrent.Callable;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.FutureTask;
-
-/**
- * Isolates the nasty details of touching the message queue.
- */
-final class QueueInterrogator {
-
- enum QueueState { EMPTY, TASK_DUE_SOON, TASK_DUE_LONG, BARRIER };
-
- private static final String TAG = "QueueInterrogator";
-
- private static final Method messageQueueNextMethod;
- private static final Field messageQueueHeadField;
- private static final int LOOKAHEAD_MILLIS = 15;
-
- private final Looper interrogatedLooper;
- private volatile MessageQueue interrogatedQueue;
-
- static {
- Method nextMethod = null;
- Field headField = null;
- try {
- nextMethod = MessageQueue.class.getDeclaredMethod("next");
- nextMethod.setAccessible(true);
-
- headField = MessageQueue.class.getDeclaredField("mMessages");
- headField.setAccessible(true);
- } catch (IllegalArgumentException e) {
- nextMethod = null;
- headField = null;
- Log.e(TAG, "Could not initialize interrogator!", e);
- } catch (NoSuchFieldException e) {
- nextMethod = null;
- headField = null;
- Log.e(TAG, "Could not initialize interrogator!", e);
- } catch (NoSuchMethodException e) {
- nextMethod = null;
- headField = null;
- Log.e(TAG, "Could not initialize interrogator!", e);
- } catch (SecurityException e) {
- nextMethod = null;
- headField = null;
- Log.e(TAG, "Could not initialize interrogator!", e);
- } finally {
- messageQueueNextMethod = nextMethod;
- messageQueueHeadField = headField;
- }
- }
-
- QueueInterrogator(Looper interrogatedLooper) {
- this.interrogatedLooper = checkNotNull(interrogatedLooper);
- checkNotNull(messageQueueHeadField);
- checkNotNull(messageQueueNextMethod);
- }
-
- // Only for use by espresso - keep package private.
- Message getNextMessage() {
- checkThread();
-
- if (null == interrogatedQueue) {
- initializeQueue();
- }
-
- try {
- return (Message) messageQueueNextMethod.invoke(Looper.myQueue());
- } catch (IllegalAccessException e) {
- throw propagate(e);
- } catch (IllegalArgumentException e) {
- throw propagate(e);
- } catch (InvocationTargetException e) {
- throw propagate(e);
- } catch (SecurityException e) {
- throw propagate(e);
- }
- }
-
- QueueState determineQueueState() {
- // may be called from any thread.
-
- if (null == interrogatedQueue) {
- initializeQueue();
- }
- synchronized (interrogatedQueue) {
- try {
- Message head = (Message) messageQueueHeadField.get(interrogatedQueue);
- if (null == head) {
- // no messages pending - AT ALL!
- return QueueState.EMPTY;
- }
- if (null == head.getTarget()) {
- // null target is a sync barrier token.
- return QueueState.BARRIER;
- } else {
- long headWhen = head.getWhen();
- long nowFuz = SystemClock.uptimeMillis() + LOOKAHEAD_MILLIS;
-
- if (nowFuz > headWhen) {
- return QueueState.TASK_DUE_SOON;
- } else {
- return QueueState.TASK_DUE_LONG;
- }
- }
- } catch (IllegalAccessException e) {
- throw propagate(e);
- }
- }
- }
-
- private void initializeQueue() {
- if (interrogatedLooper == Looper.myLooper()) {
- interrogatedQueue = Looper.myQueue();
- } else {
- Handler oneShotHandler = new Handler(interrogatedLooper);
- FutureTask<MessageQueue> queueCapture = new FutureTask<MessageQueue>(
- new Callable<MessageQueue>() {
- @Override
- public MessageQueue call() {
- return Looper.myQueue();
- }
- });
- oneShotHandler.postAtFrontOfQueue(queueCapture);
- try {
- interrogatedQueue = queueCapture.get();
- } catch (ExecutionException ee) {
- throw propagate(ee.getCause());
- } catch (InterruptedException ie) {
- throw propagate(ie);
- }
- }
- }
-
- private void checkThread() {
- checkState(interrogatedLooper == Looper.myLooper(), "Calling from non-owning thread!");
- }
-}
diff --git a/espresso/espresso-lib/src/main/java/com/google/android/apps/common/testing/ui/espresso/base/RootViewPicker.java b/espresso/espresso-lib/src/main/java/com/google/android/apps/common/testing/ui/espresso/base/RootViewPicker.java
deleted file mode 100644
index 6870aa3..0000000
--- a/espresso/espresso-lib/src/main/java/com/google/android/apps/common/testing/ui/espresso/base/RootViewPicker.java
+++ /dev/null
@@ -1,217 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.android.apps.common.testing.ui.espresso.base;
-
-import static com.google.android.apps.common.testing.ui.espresso.matcher.RootMatchers.isDialog;
-import static com.google.android.apps.common.testing.ui.espresso.matcher.RootMatchers.isFocusable;
-import static com.google.common.base.Preconditions.checkState;
-
-import com.google.android.apps.common.testing.testrunner.ActivityLifecycleMonitor;
-import com.google.android.apps.common.testing.testrunner.Stage;
-import com.google.android.apps.common.testing.ui.espresso.NoActivityResumedException;
-import com.google.android.apps.common.testing.ui.espresso.NoMatchingRootException;
-import com.google.android.apps.common.testing.ui.espresso.Root;
-import com.google.android.apps.common.testing.ui.espresso.UiController;
-import com.google.common.base.Joiner;
-import com.google.common.collect.Lists;
-
-import android.app.Activity;
-import android.os.Looper;
-import android.util.Log;
-import android.view.View;
-
-import org.hamcrest.Matcher;
-
-import java.util.Collection;
-import java.util.EnumSet;
-import java.util.List;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicReference;
-
-import javax.inject.Inject;
-import javax.inject.Provider;
-import javax.inject.Singleton;
-
-/**
- * Provides the root View of the top-most Window, with which the user can interact. View is
- * guaranteed to be in a stable state - i.e. not pending any updates from the application.
- *
- * This provider can only be accessed from the main thread.
- */
-@Singleton
-public final class RootViewPicker implements Provider<View> {
- private static final String TAG = RootViewPicker.class.getSimpleName();
-
- private final Provider<List<Root>> rootsOracle;
- private final UiController uiController;
- private final ActivityLifecycleMonitor activityLifecycleMonitor;
- private final AtomicReference<Matcher<Root>> rootMatcherRef;
-
- private List<Root> roots;
-
- @Inject
- RootViewPicker(Provider<List<Root>> rootsOracle, UiController uiController,
- ActivityLifecycleMonitor activityLifecycleMonitor,
- AtomicReference<Matcher<Root>> rootMatcherRef) {
- this.rootsOracle = rootsOracle;
- this.uiController = uiController;
- this.activityLifecycleMonitor = activityLifecycleMonitor;
- this.rootMatcherRef = rootMatcherRef;
- }
-
- @Override
- public View get() {
- checkState(Looper.getMainLooper().equals(Looper.myLooper()), "must be called on main thread.");
- Matcher<Root> rootMatcher = rootMatcherRef.get();
-
- Root root = findRoot(rootMatcher);
-
- // we only want to propagate a root view that the user can interact with and is not
- // about to relay itself out. An app should be in this state the majority of the time,
- // if we happen not to be in this state at the moment, process the queue some more
- // we should come to it quickly enough.
- int loops = 0;
-
- while (!isReady(root)) {
- if (loops < 3) {
- uiController.loopMainThreadUntilIdle();
- } else if (loops < 1001) {
-
- // loopUntil idle effectively is polling and pegs the CPU... if we don't have an update to
- // process immediately, we might have something coming very very soon.
- uiController.loopMainThreadForAtLeast(10);
- } else {
- // we've waited for the root view to be fully laid out and have window focus
- // for over 10 seconds. something is wrong.
- throw new RuntimeException(String.format("Waited for the root of the view hierarchy to have"
- + " window focus and not be requesting layout for over 10 seconds. If you specified a"
- + " non default root matcher, it may be picking a root that never takes focus."
- + " Otherwise, something is seriously wrong. Selected Root:\n%s\n. All Roots:\n%s"
- , root, Joiner.on("\n").join(roots)));
- }
-
- root = findRoot(rootMatcher);
- loops++;
- }
-
- return root.getDecorView();
- }
-
- private boolean isReady(Root root) {
- // Root is ready (i.e. UI is no longer in flux) if layout of the root view is not being
- // requested and the root view has window focus (if it is focusable).
- View rootView = root.getDecorView();
- if (!rootView.isLayoutRequested()) {
- return rootView.hasWindowFocus() || !isFocusable().matches(root);
- }
- return false;
- }
-
- private Root findRoot(Matcher<Root> rootMatcher) {
- waitForAtLeastOneActivityToBeResumed();
-
- roots = rootsOracle.get();
-
- // TODO(user): move these checks into the RootsOracle.
- if (roots.isEmpty()) {
- // Reflection broke
- throw new RuntimeException("No root window were discovered.");
- }
-
- if (roots.size() > 1) {
- // Multiple roots only occur:
- // when multiple activities are in some state of their lifecycle in the application
- // - we don't care about this, since we only want to interact with the RESUMED
- // activity, all other activities windows are not visible to the user so, out of
- // scope.
- // when a PopupWindow or PopupMenu is used
- // - this is a case where we definitely want to consider the top most window, since
- // it probably has the most useful info in it.
- // when an android.app.dialog is shown
- // - again, this is getting all the users attention, so it gets the test attention
- // too.
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, String.format("Multiple windows detected: %s", roots));
- }
- }
-
- List<Root> selectedRoots = Lists.newArrayList();
- for (Root root : roots) {
- if (rootMatcher.matches(root)) {
- selectedRoots.add(root);
- }
- }
-
- if (selectedRoots.isEmpty()) {
- throw NoMatchingRootException.create(rootMatcher, roots);
- }
-
- return reduceRoots(selectedRoots);
- }
-
- @SuppressWarnings("unused")
- private void waitForAtLeastOneActivityToBeResumed() {
- Collection<Activity> resumedActivities =
- activityLifecycleMonitor.getActivitiesInStage(Stage.RESUMED);
- if (resumedActivities.isEmpty()) {
- uiController.loopMainThreadUntilIdle();
- resumedActivities = activityLifecycleMonitor.getActivitiesInStage(Stage.RESUMED);
- }
- if (resumedActivities.isEmpty()) {
- List<Activity> activities = Lists.newArrayList();
- for (Stage s : EnumSet.range(Stage.PRE_ON_CREATE, Stage.RESTARTED)) {
- activities.addAll(activityLifecycleMonitor.getActivitiesInStage(s));
- }
- if (activities.isEmpty()) {
- throw new RuntimeException("No activities found. Did you forget to launch the activity "
- + "by calling getActivity() or startActivitySync or similar?");
- }
- // well at least there are some activities in the pipeline - lets see if they resume.
-
- long[] waitTimes =
- {10, 50, 100, 500, TimeUnit.SECONDS.toMillis(2), TimeUnit.SECONDS.toMillis(30)};
-
- for (int waitIdx = 0; waitIdx < waitTimes.length; waitIdx++) {
- Log.w(TAG, "No activity currently resumed - waiting: " + waitTimes[waitIdx]
- + "ms for one to appear.");
- uiController.loopMainThreadForAtLeast(waitTimes[waitIdx]);
- resumedActivities = activityLifecycleMonitor.getActivitiesInStage(Stage.RESUMED);
- if (!resumedActivities.isEmpty()) {
- return; // one of the pending activities has resumed
- }
- }
- throw new NoActivityResumedException("No activities in stage RESUMED. Did you forget to "
- + "launch the activity. (test.getActivity() or similar)?");
- }
- }
-
- private Root reduceRoots(List<Root> subpanels) {
- Root topSubpanel = subpanels.get(0);
- if (subpanels.size() >= 1) {
- for (Root subpanel : subpanels) {
- if (isDialog().matches(subpanel)) {
- return subpanel;
- }
- if (subpanel.getWindowLayoutParams().get().type
- > topSubpanel.getWindowLayoutParams().get().type) {
- topSubpanel = subpanel;
- }
- }
- }
- return topSubpanel;
- }
-}
diff --git a/espresso/espresso-lib/src/main/java/com/google/android/apps/common/testing/ui/espresso/base/RootsOracle.java b/espresso/espresso-lib/src/main/java/com/google/android/apps/common/testing/ui/espresso/base/RootsOracle.java
deleted file mode 100644
index a284ede..0000000
--- a/espresso/espresso-lib/src/main/java/com/google/android/apps/common/testing/ui/espresso/base/RootsOracle.java
+++ /dev/null
@@ -1,167 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.android.apps.common.testing.ui.espresso.base;
-
-import static com.google.common.base.Preconditions.checkState;
-
-import com.google.android.apps.common.testing.ui.espresso.Root;
-import com.google.common.collect.Lists;
-
-import android.os.Build;
-import android.os.Looper;
-import android.util.Log;
-import android.view.View;
-import android.view.WindowManager.LayoutParams;
-
-import java.lang.reflect.Field;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.util.Arrays;
-import java.util.List;
-
-import javax.inject.Inject;
-import javax.inject.Provider;
-import javax.inject.Singleton;
-
-/**
- * Provides access to all root views in an application.
- *
- * 95% of the time this is unnecessary and we can operate solely on current Activity's root view
- * as indicated by getWindow().getDecorView(). However in the case of popup windows, menus, and
- * dialogs the actual view hierarchy we should be operating on is not accessible thru public apis.
- *
- * In the spirit of degrading gracefully when new api levels break compatibility, callers should
- * handle a list of size 0 by assuming getWindow().getDecorView() on the currently resumed activity
- * is the sole root - this assumption will be correct often enough.
- *
- * Obviously, you need to be on the main thread to use this.
- */
-@Singleton
-final class RootsOracle implements Provider<List<Root>> {
-
- private static final String TAG = RootsOracle.class.getSimpleName();
- private static final String WINDOW_MANAGER_IMPL_CLAZZ =
- "android.view.WindowManagerImpl";
- private static final String WINDOW_MANAGER_GLOBAL_CLAZZ =
- "android.view.WindowManagerGlobal";
- private static final String VIEWS_FIELD = "mViews";
- private static final String WINDOW_PARAMS_FIELD = "mParams";
- private static final String GET_DEFAULT_IMPL = "getDefault";
- private static final String GET_GLOBAL_INSTANCE = "getInstance";
-
- private final Looper mainLooper;
- private boolean initialized;
- private Object windowManagerObj;
- private Field viewsField;
- private Field paramsField;
-
- @Inject
- RootsOracle(Looper mainLooper) {
- this.mainLooper = mainLooper;
- }
-
- @SuppressWarnings("unchecked")
- @Override
- public List<Root> get() {
- checkState(mainLooper.equals(Looper.myLooper()), "must be called on main thread.");
-
- if (!initialized) {
- initialize();
- }
-
- if (null == windowManagerObj) {
- Log.w(TAG, "No reflective access to windowmanager object.");
- return Lists.newArrayList();
- }
-
- if (null == viewsField) {
- Log.w(TAG, "No reflective access to mViews");
- return Lists.newArrayList();
- }
- if (null == paramsField) {
- Log.w(TAG, "No reflective access to mPArams");
- return Lists.newArrayList();
- }
-
- List<View> views = null;
- List<LayoutParams> params = null;
-
- try {
- if (Build.VERSION.SDK_INT < 19) {
- views = Arrays.asList((View[]) viewsField.get(windowManagerObj));
- params = Arrays.asList((LayoutParams[]) paramsField.get(windowManagerObj));
- } else {
- views = (List<View>) viewsField.get(windowManagerObj);
- params = (List<LayoutParams>) paramsField.get(windowManagerObj);
- }
- } catch (RuntimeException re) {
- Log.w(TAG, String.format("Reflective access to %s or %s on %s failed.",
- viewsField, paramsField, windowManagerObj), re);
- return Lists.newArrayList();
- } catch (IllegalAccessException iae) {
- Log.w(TAG, String.format("Reflective access to %s or %s on %s failed.",
- viewsField, paramsField, windowManagerObj), iae);
- return Lists.newArrayList();
- }
-
-
- List<Root> roots = Lists.newArrayList();
- for (int i = views.size() - 1; i > -1; i--) {
- roots.add(
- new Root.Builder()
- .withDecorView(views.get(i))
- .withWindowLayoutParams(params.get(i))
- .build());
- }
-
- return roots;
- }
-
- private void initialize() {
- initialized = true;
- String accessClass = Build.VERSION.SDK_INT > 16 ? WINDOW_MANAGER_GLOBAL_CLAZZ
- : WINDOW_MANAGER_IMPL_CLAZZ;
- String instanceMethod = Build.VERSION.SDK_INT > 16 ? GET_GLOBAL_INSTANCE : GET_DEFAULT_IMPL;
-
- try {
- Class<?> clazz = Class.forName(accessClass);
- Method getMethod = clazz.getMethod(instanceMethod);
- windowManagerObj = getMethod.invoke(null);
- viewsField = clazz.getDeclaredField(VIEWS_FIELD);
- viewsField.setAccessible(true);
- paramsField = clazz.getDeclaredField(WINDOW_PARAMS_FIELD);
- paramsField.setAccessible(true);
- } catch (InvocationTargetException ite) {
- Log.e(TAG, String.format("could not invoke: %s on %s", instanceMethod, accessClass),
- ite.getCause());
- } catch (ClassNotFoundException cnfe) {
- Log.e(TAG, String.format("could not find class: %s", accessClass), cnfe);
- } catch (NoSuchFieldException nsfe) {
- Log.e(TAG, String.format("could not find field: %s or %s on %s", WINDOW_PARAMS_FIELD,
- VIEWS_FIELD, accessClass), nsfe);
- } catch (NoSuchMethodException nsme) {
- Log.e(TAG, String.format("could not find method: %s on %s", instanceMethod, accessClass),
- nsme);
- } catch (RuntimeException re) {
- Log.e(TAG, String.format("reflective setup failed using obj: %s method: %s field: %s",
- accessClass, instanceMethod, VIEWS_FIELD), re);
- } catch (IllegalAccessException iae) {
- Log.e(TAG, String.format("reflective setup failed using obj: %s method: %s field: %s",
- accessClass, instanceMethod, VIEWS_FIELD), iae);
- }
- }
-}
diff --git a/espresso/espresso-lib/src/main/java/com/google/android/apps/common/testing/ui/espresso/base/SdkAsyncTask.java b/espresso/espresso-lib/src/main/java/com/google/android/apps/common/testing/ui/espresso/base/SdkAsyncTask.java
deleted file mode 100644
index b28255e..0000000
--- a/espresso/espresso-lib/src/main/java/com/google/android/apps/common/testing/ui/espresso/base/SdkAsyncTask.java
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.android.apps.common.testing.ui.espresso.base;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-import javax.inject.Qualifier;
-
-/**
- * Annotates a AsyncTaskMonitor as monitoring the SdkAsyncTask pool
- */
-@Qualifier
-@Retention(RetentionPolicy.RUNTIME)
-@interface SdkAsyncTask { }
diff --git a/espresso/espresso-lib/src/main/java/com/google/android/apps/common/testing/ui/espresso/base/ThreadPoolExecutorExtractor.java b/espresso/espresso-lib/src/main/java/com/google/android/apps/common/testing/ui/espresso/base/ThreadPoolExecutorExtractor.java
deleted file mode 100644
index 1a719a3..0000000
--- a/espresso/espresso-lib/src/main/java/com/google/android/apps/common/testing/ui/espresso/base/ThreadPoolExecutorExtractor.java
+++ /dev/null
@@ -1,154 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.android.apps.common.testing.ui.espresso.base;
-
-import com.google.common.base.Optional;
-
-import android.os.Build;
-import android.os.Handler;
-import android.os.Looper;
-
-import java.lang.reflect.Field;
-import java.util.concurrent.Callable;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.FutureTask;
-import java.util.concurrent.ThreadPoolExecutor;
-
-import javax.inject.Inject;
-import javax.inject.Singleton;
-
-/**
- * Extracts ThreadPoolExecutors used by pieces of android.
- *
- * We do some work to ensure that we load the classes containing these thread pools
- * on the main thread, since they may have static initialization that assumes access
- * to the main looper.
- */
-@Singleton
-final class ThreadPoolExecutorExtractor {
- private static final String ASYNC_TASK_CLASS_NAME = "android.os.AsyncTask";
- private static final String MODERN_ASYNC_TASK_CLASS_NAME =
- "android.support.v4.content.ModernAsyncTask";
- private static final String MODERN_ASYNC_TASK_FIELD_NAME = "THREAD_POOL_EXECUTOR";
- private static final String LEGACY_ASYNC_TASK_FIELD_NAME = "sExecutor";
- private final Handler mainHandler;
-
- @Inject
- ThreadPoolExecutorExtractor(Looper looper) {
- mainHandler = new Handler(looper);
- }
-
-
- public ThreadPoolExecutor getAsyncTaskThreadPool() {
- FutureTask<Optional<ThreadPoolExecutor>> getTask = null;
- if (Build.VERSION.SDK_INT < 11) {
- getTask = new FutureTask<Optional<ThreadPoolExecutor>>(LEGACY_ASYNC_TASK_EXECUTOR);
- } else {
- getTask = new FutureTask<Optional<ThreadPoolExecutor>>(POST_HONEYCOMB_ASYNC_TASK_EXECUTOR);
- }
-
- try {
- return runOnMainThread(getTask).get().get();
- } catch (InterruptedException ie) {
- throw new RuntimeException("Interrupted while trying to get the async task executor!", ie);
- } catch (ExecutionException ee) {
- throw new RuntimeException(ee.getCause());
- }
- }
-
- public Optional<ThreadPoolExecutor> getCompatAsyncTaskThreadPool() {
- try {
- return runOnMainThread(
- new FutureTask<Optional<ThreadPoolExecutor>>(MODERN_ASYNC_TASK_EXTRACTOR)).get();
- } catch (InterruptedException ie) {
- throw new RuntimeException("Interrupted while trying to get the compat async executor!", ie);
- } catch (ExecutionException ee) {
- throw new RuntimeException(ee.getCause());
- }
- }
-
- private <T> FutureTask<T> runOnMainThread(final FutureTask<T> futureToRun) {
- if (Looper.myLooper() != Looper.getMainLooper()) {
- final CountDownLatch latch = new CountDownLatch(1);
- mainHandler.post(new Runnable() {
- @Override
- public void run() {
- try {
- futureToRun.run();
- } finally {
- latch.countDown();
- }
- }
- });
- try {
- latch.await();
- } catch (InterruptedException ie) {
- if (!futureToRun.isDone()) {
- throw new RuntimeException("Interrupted while waiting for task to complete.");
- }
- }
- } else {
- futureToRun.run();
- }
-
- return futureToRun;
- }
-
- private static final Callable<Optional<ThreadPoolExecutor>> MODERN_ASYNC_TASK_EXTRACTOR =
- new Callable<Optional<ThreadPoolExecutor>>() {
- @Override
- public Optional<ThreadPoolExecutor> call() throws Exception {
- try {
- Class<?> modernClazz = Class.forName(MODERN_ASYNC_TASK_CLASS_NAME);
- Field executorField = modernClazz.getField(MODERN_ASYNC_TASK_FIELD_NAME);
- return Optional.of((ThreadPoolExecutor) executorField.get(null));
- } catch (ClassNotFoundException cnfe) {
- return Optional.<ThreadPoolExecutor>absent();
- }
- }
- };
-
- private static final Callable<Class<?>> LOAD_ASYNC_TASK_CLASS =
- new Callable<Class<?>>() {
- @Override
- public Class<?> call() throws Exception {
- return Class.forName(ASYNC_TASK_CLASS_NAME);
- }
- };
-
- private static final Callable<Optional<ThreadPoolExecutor>> LEGACY_ASYNC_TASK_EXECUTOR =
- new Callable<Optional<ThreadPoolExecutor>>() {
- @Override
- public Optional<ThreadPoolExecutor> call() throws Exception {
- Field executorField = LOAD_ASYNC_TASK_CLASS.call()
- .getDeclaredField(LEGACY_ASYNC_TASK_FIELD_NAME);
- executorField.setAccessible(true);
- return Optional.of((ThreadPoolExecutor) executorField.get(null));
- }
- };
-
- private static final Callable<Optional<ThreadPoolExecutor>> POST_HONEYCOMB_ASYNC_TASK_EXECUTOR =
- new Callable<Optional<ThreadPoolExecutor>>() {
- @Override
- public Optional<ThreadPoolExecutor> call() throws Exception {
- Field executorField = LOAD_ASYNC_TASK_CLASS.call()
- .getField(MODERN_ASYNC_TASK_FIELD_NAME);
- return Optional.of((ThreadPoolExecutor) executorField.get(null));
- }
- };
-}
diff --git a/espresso/espresso-lib/src/main/java/com/google/android/apps/common/testing/ui/espresso/base/UiControllerImpl.java b/espresso/espresso-lib/src/main/java/com/google/android/apps/common/testing/ui/espresso/base/UiControllerImpl.java
deleted file mode 100644
index c1aaa5c..0000000
--- a/espresso/espresso-lib/src/main/java/com/google/android/apps/common/testing/ui/espresso/base/UiControllerImpl.java
+++ /dev/null
@@ -1,535 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.android.apps.common.testing.ui.espresso.base;
-
-import static com.google.common.base.Preconditions.checkArgument;
-import static com.google.common.base.Preconditions.checkNotNull;
-import static com.google.common.base.Preconditions.checkState;
-import static com.google.common.base.Throwables.propagate;
-
-import com.google.android.apps.common.testing.ui.espresso.IdlingPolicies;
-import com.google.android.apps.common.testing.ui.espresso.IdlingPolicy;
-import com.google.android.apps.common.testing.ui.espresso.InjectEventSecurityException;
-import com.google.android.apps.common.testing.ui.espresso.UiController;
-import com.google.android.apps.common.testing.ui.espresso.base.IdlingResourceRegistry.IdleNotificationCallback;
-import com.google.android.apps.common.testing.ui.espresso.base.QueueInterrogator.QueueState;
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Optional;
-import com.google.common.collect.Lists;
-
-import android.annotation.SuppressLint;
-import android.os.Build;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.os.SystemClock;
-import android.util.Log;
-import android.view.KeyCharacterMap;
-import android.view.KeyEvent;
-import android.view.MotionEvent;
-
-import java.util.BitSet;
-import java.util.EnumSet;
-import java.util.List;
-import java.util.concurrent.Callable;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.FutureTask;
-
-import javax.inject.Inject;
-import javax.inject.Singleton;
-
-/**
- * Implementation of {@link UiController}.
- */
-@Singleton
-final class UiControllerImpl implements UiController, Handler.Callback {
-
- private static final String TAG = UiControllerImpl.class.getSimpleName();
-
- private static final Callable<Void> NO_OP = new Callable<Void>() {
- @Override
- public Void call() {
- return null;
- }
- };
-
- /**
- * Responsible for signaling a particular condition is met / verifying that signal.
- */
- enum IdleCondition {
- DELAY_HAS_PAST,
- ASYNC_TASKS_HAVE_IDLED,
- COMPAT_TASKS_HAVE_IDLED,
- KEY_INJECT_HAS_COMPLETED,
- MOTION_INJECTION_HAS_COMPLETED,
- DYNAMIC_TASKS_HAVE_IDLED;
-
- /**
- * Checks whether this condition has been signaled.
- */
- public boolean isSignaled(BitSet conditionSet) {
- return conditionSet.get(ordinal());
- }
-
- /**
- * Resets the signal state for this condition.
- */
- public void reset(BitSet conditionSet) {
- conditionSet.set(ordinal(), false);
- }
-
- /**
- * Creates a message that when sent will raise the signal of this condition.
- */
- public Message createSignal(Handler handler, int myGeneration) {
- return Message.obtain(handler, ordinal(), myGeneration, 0, null);
- }
-
- /**
- * Handles a message that is raising a signal and updates the condition set accordingly.
- * Messages from a previous generation will be ignored.
- */
- public static boolean handleMessage(Message message, BitSet conditionSet,
- int currentGeneration) {
- IdleCondition [] allConditions = values();
- if (message.what < 0 || message.what >= allConditions.length) {
- return false;
- } else {
- IdleCondition condition = allConditions[message.what];
- if (message.arg1 == currentGeneration) {
- condition.signal(conditionSet);
- } else {
- Log.w(TAG, "ignoring signal of: " + condition + " from previous generation: " +
- message.arg1 + " current generation: " + currentGeneration);
- }
- return true;
- }
- }
-
- public static BitSet createConditionSet() {
- return new BitSet(values().length);
- }
-
- /**
- * Requests that the given bitset be updated to indicate that this condition has been
- * signaled.
- */
- protected void signal(BitSet conditionSet) {
- conditionSet.set(ordinal());
- }
- }
-
- private final EventInjector eventInjector;
- private final BitSet conditionSet;
- private final AsyncTaskPoolMonitor asyncTaskMonitor;
- private final Optional<AsyncTaskPoolMonitor> compatTaskMonitor;
- private final IdlingResourceRegistry idlingResourceRegistry;
- private final ExecutorService keyEventExecutor = Executors.newSingleThreadExecutor();
- private final QueueInterrogator queueInterrogator;
- private final Looper mainLooper;
-
- private Handler controllerHandler;
- // only updated on main thread.
- private boolean looping = false;
- private int generation = 0;
-
- @VisibleForTesting
- @Inject
- UiControllerImpl(EventInjector eventInjector,
- @SdkAsyncTask AsyncTaskPoolMonitor asyncTaskMonitor,
- @CompatAsyncTask Optional<AsyncTaskPoolMonitor> compatTaskMonitor,
- IdlingResourceRegistry registry,
- Looper mainLooper) {
- this.eventInjector = checkNotNull(eventInjector);
- this.asyncTaskMonitor = checkNotNull(asyncTaskMonitor);
- this.compatTaskMonitor = checkNotNull(compatTaskMonitor);
- this.conditionSet = IdleCondition.createConditionSet();
- this.idlingResourceRegistry = checkNotNull(registry);
- this.mainLooper = checkNotNull(mainLooper);
- this.queueInterrogator = new QueueInterrogator(mainLooper);
- }
-
- @SuppressWarnings("deprecation")
- @Override
- public boolean injectKeyEvent(final KeyEvent event) throws InjectEventSecurityException {
- checkNotNull(event);
- checkState(Looper.myLooper() == mainLooper, "Expecting to be on main thread!");
- initialize();
- loopMainThreadUntilIdle();
-
- FutureTask<Boolean> injectTask = new SignalingTask<Boolean>(
- new Callable<Boolean>() {
- @Override
- public Boolean call() throws Exception {
- return eventInjector.injectKeyEvent(event);
- }
- },
- IdleCondition.KEY_INJECT_HAS_COMPLETED,
- generation);
-
- // Inject the key event.
- keyEventExecutor.submit(injectTask);
-
- loopUntil(IdleCondition.KEY_INJECT_HAS_COMPLETED);
-
- try {
- checkState(injectTask.isDone(), "Key injection was signaled - but it wasnt done.");
- return injectTask.get();
- } catch (ExecutionException ee) {
- if (ee.getCause() instanceof InjectEventSecurityException) {
- throw (InjectEventSecurityException) ee.getCause();
- } else {
- throw new RuntimeException(ee.getCause());
- }
- } catch (InterruptedException neverHappens) {
- // we only call get() after done() is signaled.
- // we should never block.
- throw new RuntimeException("impossible.", neverHappens);
- }
- }
-
- @Override
- public boolean injectMotionEvent(final MotionEvent event) throws InjectEventSecurityException {
- checkNotNull(event);
- checkState(Looper.myLooper() == mainLooper, "Expecting to be on main thread!");
- initialize();
-
- FutureTask<Boolean> injectTask = new SignalingTask<Boolean>(
- new Callable<Boolean>() {
- @Override
- public Boolean call() throws Exception {
- return eventInjector.injectMotionEvent(event);
- }
- },
- IdleCondition.MOTION_INJECTION_HAS_COMPLETED,
- generation);
- keyEventExecutor.submit(injectTask);
- loopUntil(IdleCondition.MOTION_INJECTION_HAS_COMPLETED);
- try {
- checkState(injectTask.isDone(), "Key injection was signaled - but it wasnt done.");
- return injectTask.get();
- } catch (ExecutionException ee) {
- if (ee.getCause() instanceof InjectEventSecurityException) {
- throw (InjectEventSecurityException) ee.getCause();
- } else {
- throw propagate(ee.getCause() != null ? ee.getCause() : ee);
- }
- } catch (InterruptedException neverHappens) {
- // we only call get() after done() is signaled.
- // we should never block.
- throw propagate(neverHappens);
- } finally {
- loopMainThreadUntilIdle();
- }
- }
-
- @Override
- public boolean injectString(String str) throws InjectEventSecurityException {
- checkNotNull(str);
- checkState(Looper.myLooper() == mainLooper, "Expecting to be on main thread!");
- initialize();
-
- // No-op if string is empty.
- if (str.length() == 0) {
- Log.w(TAG, "Supplied string is empty resulting in no-op (nothing is typed).");
- return true;
- }
-
- boolean eventInjected = false;
- KeyCharacterMap keyCharacterMap = getKeyCharacterMap();
-
- // TODO(user): Investigate why not use (as suggested in javadoc of keyCharacterMap.getEvents):
- // http://developer.android.com/reference/android/view/KeyEvent.html#KeyEvent(long,
- // java.lang.String, int, int)
- KeyEvent[] events = keyCharacterMap.getEvents(str.toCharArray());
- checkNotNull(events, "Failed to get events for string " + str);
- Log.d(TAG, String.format("Injecting string: \"%s\"", str));
-
- for (KeyEvent event : events) {
- checkNotNull(event, String.format("Failed to get event for character (%c) with key code (%s)",
- event.getKeyCode(), event.getUnicodeChar()));
-
- eventInjected = false;
- for (int attempts = 0; !eventInjected && attempts < 4; attempts++) {
- attempts++;
-
- // We have to change the time of an event before injecting it because
- // all KeyEvents returned by KeyCharacterMap.getEvents() have the same
- // time stamp and the system rejects too old events. Hence, it is
- // possible for an event to become stale before it is injected if it
- // takes too long to inject the preceding ones.
- event = KeyEvent.changeTimeRepeat(event, SystemClock.uptimeMillis(), 0);
- eventInjected = injectKeyEvent(event);
- }
-
- if (!eventInjected) {
- Log.e(TAG, String.format("Failed to inject event for character (%c) with key code (%s)",
- event.getUnicodeChar(), event.getKeyCode()));
- break;
- }
- }
-
- return eventInjected;
- }
-
- @SuppressLint("InlinedApi")
- @VisibleForTesting
- @SuppressWarnings("deprecation")
- public static KeyCharacterMap getKeyCharacterMap() {
- KeyCharacterMap keyCharacterMap = null;
-
- // KeyCharacterMap.VIRTUAL_KEYBOARD is present from API11.
- // For earlier APIs we use KeyCharacterMap.BUILT_IN_KEYBOARD
- if (Build.VERSION.SDK_INT < 11) {
- keyCharacterMap = KeyCharacterMap.load(KeyCharacterMap.BUILT_IN_KEYBOARD);
- } else {
- keyCharacterMap = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD);
- }
- return keyCharacterMap;
- }
-
-
- @Override
- public void loopMainThreadUntilIdle() {
- initialize();
- checkState(Looper.myLooper() == mainLooper, "Expecting to be on main thread!");
- do {
- EnumSet<IdleCondition> condChecks = EnumSet.noneOf(IdleCondition.class);
- if (!asyncTaskMonitor.isIdleNow()) {
- asyncTaskMonitor.notifyWhenIdle(new SignalingTask<Void>(NO_OP,
- IdleCondition.ASYNC_TASKS_HAVE_IDLED, generation));
-
- condChecks.add(IdleCondition.ASYNC_TASKS_HAVE_IDLED);
- }
-
- if (!compatIdle()) {
- compatTaskMonitor.get().notifyWhenIdle(new SignalingTask<Void>(NO_OP,
- IdleCondition.COMPAT_TASKS_HAVE_IDLED, generation));
- condChecks.add(IdleCondition.COMPAT_TASKS_HAVE_IDLED);
- }
-
- if (!idlingResourceRegistry.allResourcesAreIdle()) {
- final IdlingPolicy warning = IdlingPolicies.getDynamicIdlingResourceWarningPolicy();
- final IdlingPolicy error = IdlingPolicies.getDynamicIdlingResourceErrorPolicy();
- final SignalingTask<Void> idleSignal = new SignalingTask<Void>(NO_OP,
- IdleCondition.DYNAMIC_TASKS_HAVE_IDLED, generation);
- idlingResourceRegistry.notifyWhenAllResourcesAreIdle(new IdleNotificationCallback() {
- @Override
- public void resourcesStillBusyWarning(List<String> busyResourceNames) {
- warning.handleTimeout(busyResourceNames, "IdlingResources are still busy!");
- }
-
- @Override
- public void resourcesHaveTimedOut(List<String> busyResourceNames) {
- error.handleTimeout(busyResourceNames, "IdlingResources have timed out!");
- controllerHandler.post(idleSignal);
- }
-
- @Override
- public void allResourcesIdle() {
- controllerHandler.post(idleSignal);
- }
- });
- condChecks.add(IdleCondition.DYNAMIC_TASKS_HAVE_IDLED);
- }
-
- try {
- loopUntil(condChecks);
- } finally {
- asyncTaskMonitor.cancelIdleMonitor();
- if (compatTaskMonitor.isPresent()) {
- compatTaskMonitor.get().cancelIdleMonitor();
- }
- idlingResourceRegistry.cancelIdleMonitor();
- }
- } while (!asyncTaskMonitor.isIdleNow() || !compatIdle()
- || !idlingResourceRegistry.allResourcesAreIdle());
-
- }
-
- private boolean compatIdle() {
- if (compatTaskMonitor.isPresent()) {
- return compatTaskMonitor.get().isIdleNow();
- } else {
- return true;
- }
- }
-
- @Override
- public void loopMainThreadForAtLeast(long millisDelay) {
- initialize();
- checkState(Looper.myLooper() == mainLooper, "Expecting to be on main thread!");
- checkState(!IdleCondition.DELAY_HAS_PAST.isSignaled(conditionSet), "recursion detected!");
-
- checkArgument(millisDelay > 0);
- controllerHandler.postDelayed(new SignalingTask(NO_OP, IdleCondition.DELAY_HAS_PAST,
- generation),
- millisDelay);
- loopUntil(IdleCondition.DELAY_HAS_PAST);
- loopMainThreadUntilIdle();
- }
-
- @Override
- public boolean handleMessage(Message msg) {
- if (!IdleCondition.handleMessage(msg, conditionSet, generation)) {
- Log.i(TAG, "Unknown message type: " + msg);
- return false;
- } else {
- return true;
- }
- }
-
- private void loopUntil(IdleCondition condition) {
- loopUntil(EnumSet.of(condition));
- }
-
- /**
- * Loops the main thread until all IdleConditions have been signaled.
- *
- * Once they've been signaled, the conditions are reset and the generation value
- * is incremented.
- *
- * Signals should only be raised thru SignalingTask instances, and care should be
- * taken to ensure that the signaling task is created before loopUntil is called.
- *
- * Good:
- * idlingType.runOnIdle(new SignalingTask(NO_OP, IdleCondition.MY_IDLE_CONDITION, generation));
- * loopUntil(IdleCondition.MY_IDLE_CONDITION);
- *
- * Bad:
- * idlingType.runOnIdle(new CustomCallback() {
- * @Override
- * public void itsDone() {
- * // oh no - The creation of this signaling task is delayed until this method is
- * // called, so it will not have the right value for generation.
- * new SignalingTask(NO_OP, IdleCondition.MY_IDLE_CONDITION, generation).run();
- * }
- * })
- * loopUntil(IdleCondition.MY_IDLE_CONDITION);
- */
- private void loopUntil(EnumSet<IdleCondition> conditions) {
- checkState(!looping, "Recursive looping detected!");
- looping = true;
- IdlingPolicy masterIdlePolicy = IdlingPolicies.getMasterIdlingPolicy();
- try {
- int loopCount = 0;
- long start = SystemClock.uptimeMillis();
- long end = start + masterIdlePolicy.getIdleTimeoutUnit().toMillis(
- masterIdlePolicy.getIdleTimeout());
- while (SystemClock.uptimeMillis() < end) {
- boolean conditionsMet = true;
- boolean shouldLogConditionState = loopCount > 0 && loopCount % 100 == 0;
-
- for (IdleCondition condition : conditions) {
- if (!condition.isSignaled(conditionSet)) {
- conditionsMet = false;
- if (shouldLogConditionState) {
- Log.w(TAG, "Waiting for: " + condition.name() + " for " + loopCount + " iterations.");
- } else {
- break;
- }
- }
- }
-
- if (conditionsMet) {
- QueueState queueState = queueInterrogator.determineQueueState();
- if (queueState == QueueState.EMPTY || queueState == QueueState.TASK_DUE_LONG) {
- return;
- } else {
- Log.v(
- "ESP_TRACE",
-
- "Barrier detected or task avaliable for running shortly.");
- }
- }
-
- Message message = queueInterrogator.getNextMessage();
- String callbackString = "unknown";
- String messageString = "unknown";
- try {
- if (null == message.getCallback()) {
- callbackString = "no callback.";
- } else {
- callbackString = message.getCallback().toString();
- }
- messageString = message.toString();
- } catch (NullPointerException e) {
- /*
- * Ignore. android.app.ActivityThread$ActivityClientRecord#toString() fails for API level
- * 15.
- */
- }
-
- Log.v(
- "ESP_TRACE",
- String.format("%s: MessageQueue.next(): %s, with target: %s, callback: %s", TAG,
- messageString, message.getTarget().getClass().getCanonicalName(), callbackString));
- message.getTarget().dispatchMessage(message);
- message.recycle();
- loopCount++;
- }
- List<String> idleConditions = Lists.newArrayList();
- for (IdleCondition condition : conditions) {
- if (!condition.isSignaled(conditionSet)) {
- idleConditions.add(condition.name());
- }
- }
- masterIdlePolicy.handleTimeout(idleConditions, String.format(
- "Looped for %s iterations over %s %s.", loopCount, masterIdlePolicy.getIdleTimeout(),
- masterIdlePolicy.getIdleTimeoutUnit().name()));
- } finally {
- looping = false;
- generation++;
- for (IdleCondition condition : conditions) {
- condition.reset(conditionSet);
- }
- }
- }
-
-
- private void initialize() {
- if (controllerHandler == null) {
- controllerHandler = new Handler(this);
- }
- }
-
-
- /**
- * Encapsulates posting a signal message to update the conditions set after a task has
- * executed.
- */
- private class SignalingTask<T> extends FutureTask<T> {
-
- private final IdleCondition condition;
- private final int myGeneration;
-
- public SignalingTask(Callable<T> callable, IdleCondition condition, int myGeneration) {
- super(callable);
- this.condition = checkNotNull(condition);
- this.myGeneration = myGeneration;
- }
-
- @Override
- protected void done() {
- controllerHandler.sendMessage(condition.createSignal(controllerHandler, myGeneration));
- }
-
- }
-
-}
diff --git a/espresso/espresso-lib/src/main/java/com/google/android/apps/common/testing/ui/espresso/base/ViewFinderImpl.java b/espresso/espresso-lib/src/main/java/com/google/android/apps/common/testing/ui/espresso/base/ViewFinderImpl.java
deleted file mode 100644
index 30e0658..0000000
--- a/espresso/espresso-lib/src/main/java/com/google/android/apps/common/testing/ui/espresso/base/ViewFinderImpl.java
+++ /dev/null
@@ -1,133 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.android.apps.common.testing.ui.espresso.base;
-
-import static com.google.android.apps.common.testing.ui.espresso.util.TreeIterables.breadthFirstViewTraversal;
-import static com.google.common.base.Preconditions.checkNotNull;
-import static com.google.common.base.Preconditions.checkState;
-
-import com.google.android.apps.common.testing.ui.espresso.AmbiguousViewMatcherException;
-import com.google.android.apps.common.testing.ui.espresso.NoMatchingViewException;
-import com.google.android.apps.common.testing.ui.espresso.ViewFinder;
-import com.google.android.apps.common.testing.ui.espresso.matcher.ViewMatchers;
-import com.google.common.base.Joiner;
-import com.google.common.base.Optional;
-import com.google.common.base.Predicate;
-import com.google.common.collect.Iterables;
-import com.google.common.collect.Iterators;
-import com.google.common.collect.Lists;
-
-import android.os.Looper;
-import android.view.View;
-import android.widget.AdapterView;
-
-import org.hamcrest.Matcher;
-
-import java.util.Iterator;
-import java.util.List;
-
-import javax.inject.Inject;
-import javax.inject.Provider;
-
-/**
- * Implementation of {@link ViewFinder}.
- */
-// TODO(user): in the future we may want to collect stats here about the size of the view
-// hierarchy, average matcher execution time, warn when matchers take too long to execute, etc.
-public final class ViewFinderImpl implements ViewFinder {
-
- private final Matcher<View> viewMatcher;
- private final Provider<View> rootViewProvider;
-
- @Inject
- ViewFinderImpl(Matcher<View> viewMatcher, Provider<View> rootViewProvider) {
- this.viewMatcher = viewMatcher;
- this.rootViewProvider = rootViewProvider;
- }
-
- @Override
- public View getView() throws AmbiguousViewMatcherException, NoMatchingViewException {
- checkMainThread();
- final Predicate<View> matcherPredicate = new MatcherPredicateAdapter<View>(
- checkNotNull(viewMatcher));
-
- View root = rootViewProvider.get();
- Iterator<View> matchedViewIterator = Iterables.filter(
- breadthFirstViewTraversal(root),
- matcherPredicate).iterator();
-
- View matchedView = null;
-
- while (matchedViewIterator.hasNext()) {
- if (matchedView != null) {
- // Ambiguous!
- throw new AmbiguousViewMatcherException.Builder()
- .withViewMatcher(viewMatcher)
- .withRootView(root)
- .withView1(matchedView)
- .withView2(matchedViewIterator.next())
- .withOtherAmbiguousViews(Iterators.toArray(matchedViewIterator, View.class))
- .build();
- } else {
- matchedView = matchedViewIterator.next();
- }
- }
- if (null == matchedView) {
- final Predicate<View> adapterViewPredicate = new MatcherPredicateAdapter<View>(
- ViewMatchers.isAssignableFrom(AdapterView.class));
- List<View> adapterViews = Lists.newArrayList(
- Iterables.filter(breadthFirstViewTraversal(root), adapterViewPredicate).iterator());
- if (adapterViews.isEmpty()) {
- throw new NoMatchingViewException.Builder()
- .withViewMatcher(viewMatcher)
- .withRootView(root)
- .build();
- }
-
- String warning = String.format("\nIf the target view is not part of the view hierarchy, you "
- + "may need to use Espresso.onData to load it from one of the following AdapterViews:%s"
- , Joiner.on("\n- ").join(adapterViews));
- throw new NoMatchingViewException.Builder()
- .withViewMatcher(viewMatcher)
- .withRootView(root)
- .withAdapterViews(adapterViews)
- .withAdapterViewWarning(Optional.of(warning))
- .build();
- } else {
- return matchedView;
- }
- }
-
- private void checkMainThread() {
- checkState(Thread.currentThread().equals(Looper.getMainLooper().getThread()),
- "Executing a query on the view hierarchy outside of the main thread (on: %s)",
- Thread.currentThread().getName());
- }
-
- private static class MatcherPredicateAdapter<T> implements Predicate<T> {
- private final Matcher<? super T> matcher;
-
- private MatcherPredicateAdapter(Matcher<? super T> matcher) {
- this.matcher = checkNotNull(matcher);
- }
-
- @Override
- public boolean apply(T input) {
- return matcher.matches(input);
- }
- }
-}
diff --git a/espresso/espresso-lib/src/main/java/com/google/android/apps/common/testing/ui/espresso/base/WindowManagerEventInjectionStrategy.java b/espresso/espresso-lib/src/main/java/com/google/android/apps/common/testing/ui/espresso/base/WindowManagerEventInjectionStrategy.java
deleted file mode 100644
index 05792e7..0000000
--- a/espresso/espresso-lib/src/main/java/com/google/android/apps/common/testing/ui/espresso/base/WindowManagerEventInjectionStrategy.java
+++ /dev/null
@@ -1,150 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.android.apps.common.testing.ui.espresso.base;
-
-import static com.google.common.base.Preconditions.checkState;
-import static com.google.common.base.Throwables.propagate;
-
-import com.google.android.apps.common.testing.ui.espresso.InjectEventSecurityException;
-
-import android.os.Build;
-import android.os.IBinder;
-import android.util.Log;
-import android.view.KeyEvent;
-import android.view.MotionEvent;
-
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-
-/**
- * An {@link EventInjectionStrategy} that uses the window manager to inject {@link MotionEvent}s.
- * This strategy supports API level 15 and below.
- */
-final class WindowManagerEventInjectionStrategy implements EventInjectionStrategy {
- private static final String TAG = WindowManagerEventInjectionStrategy.class.getSimpleName();
-
-
- WindowManagerEventInjectionStrategy() {
- checkState(Build.VERSION.SDK_INT >= 7 && Build.VERSION.SDK_INT <= 15, "Unsupported API level.");
- }
-
- // Reflection members.
- private boolean initComplete;
- private Object wmInstance;
- private Method injectInputKeyEventMethod;
- private Method injectInputMotionEventMethod;
-
- void initialize() {
- if (initComplete) {
- return;
- }
-
- try {
- Log.d(TAG, "Trying to create injection strategy.");
-
- Class<?> serviceManagerClassObj = Class.forName("android.os.ServiceManager");
- Method windowServiceMethod =
- serviceManagerClassObj.getDeclaredMethod("getService", String.class);
- windowServiceMethod.setAccessible(true);
-
- Object windowServiceBinderObj = windowServiceMethod.invoke(serviceManagerClassObj, "window");
-
- Class<?> windowManagerStubObject = Class.forName("android.view.IWindowManager$Stub");
- Method asInterfaceMethod =
- windowManagerStubObject.getDeclaredMethod("asInterface", IBinder.class);
- asInterfaceMethod.setAccessible(true);
-
- wmInstance = asInterfaceMethod.invoke(windowManagerStubObject, windowServiceBinderObj);
-
- injectInputMotionEventMethod = wmInstance.getClass()
- .getDeclaredMethod("injectPointerEvent", MotionEvent.class, Boolean.TYPE);
- injectInputMotionEventMethod.setAccessible(true);
-
- injectInputKeyEventMethod =
- wmInstance.getClass().getDeclaredMethod("injectKeyEvent", KeyEvent.class, Boolean.TYPE);
- injectInputMotionEventMethod.setAccessible(true);
-
- initComplete = true;
- } catch (ClassNotFoundException e) {
- propagate(e);
- } catch (IllegalAccessException e) {
- propagate(e);
- } catch (IllegalArgumentException e) {
- propagate(e);
- } catch (InvocationTargetException e) {
- propagate(e);
- } catch (NoSuchMethodException e) {
- propagate(e);
- } catch (SecurityException e) {
- propagate(e);
- }
- }
-
- @Override
- public boolean injectKeyEvent(KeyEvent keyEvent) throws InjectEventSecurityException {
- try {
- // From javadoc of com.android.server.WindowManagerService.injectKeyEvent:
- // @param sync If true, wait for the event to be completed before returning to the caller.
- // @return true if event was dispatched, false if it was dropped for any reason
- //
- // Key events are delivered OFF the main thread, and we block until they are processed.
- return (Boolean) injectInputKeyEventMethod.invoke(wmInstance, keyEvent, true);
- } catch (IllegalAccessException e) {
- propagate(e);
- } catch (IllegalArgumentException e) {
- propagate(e);
- } catch (InvocationTargetException e) {
- Throwable cause = e.getCause();
- if (cause instanceof SecurityException) {
- throw new InjectEventSecurityException(cause);
- }
- propagate(e);
- } catch (SecurityException e) {
- throw new InjectEventSecurityException(e);
- }
- return false;
- }
-
- @Override
- public boolean injectMotionEvent(MotionEvent motionEvent) throws InjectEventSecurityException {
- try {
- // From javadoc of com.android.server.WindowManagerService.injectKeyEvent:
- // @param sync If true, wait for the event to be completed before returning to the caller.
- // @return true if event was dispatched, false if it was dropped for any reason
- //
- // We inject the pointer with sync=true to ensure the event is dispatched before control
- // is returned to our code.
- return (Boolean) injectInputMotionEventMethod.invoke(
- wmInstance,
- motionEvent,
- true /* sync */);
- } catch (IllegalAccessException e) {
- propagate(e);
- } catch (IllegalArgumentException e) {
- propagate(e);
- } catch (InvocationTargetException e) {
- Throwable cause = e.getCause();
- if (cause instanceof SecurityException) {
- throw new InjectEventSecurityException(cause);
- }
- propagate(e);
- } catch (SecurityException e) {
- throw new InjectEventSecurityException(e);
- }
- return false;
- }
-}