/* * Copyright (C) 2016 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 Lice`nse 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 androidx.test.espresso.idling.net; import android.os.Handler; import android.os.Looper; import android.support.annotation.VisibleForTesting; import android.util.Log; import androidx.test.espresso.IdlingResource; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.regex.Pattern; /** * An implementation of {@link IdlingResource} useful for monitoring idleness of network traffic. * *
This is similar to {@link androidx.test.espresso.contrib.CountingIdlingResource}, with * the additional idleness constraint that the counter must be 0 for a set period of time before the * resource becomes idle. * *
A network timeout is required to be reasonably sure that the webview has finished loading. * Imagine the case where each response that comes back causes another request to be made until * loading is complete. The counter will go from 0->1->0->1->0->1..., but we don't want to report * the webview as idle each time this happens. * *
This API is currently in beta.
*/
public class UriIdlingResource implements IdlingResource {
private static final String TAG = "UriIdlingResource";
private final String resourceName;
private final long timeoutMs;
private final boolean debug;
// Read and modified from multiple threads
private final AtomicInteger counter = new AtomicInteger(0);
private final CopyOnWriteArrayList All request URIs are checked against all patterns, and matches are ignored for the purposes
* of detecting when the webview is idle.
*
* Ignored patterns can only be added when the webview is idle.
*/
public void ignoreUri(Pattern pattern) {
if (!isIdleNow()) {
Log.e(TAG, "Ignored patterns can only be added when the resource is idle.");
} else {
ignoredRegexes.add(pattern);
}
}
/**
* Called when a request is made.
*
* If the URI is not blacklisted the idle counter is incremented.
*/
public void beginLoad(String uri) {
if (uriIsIgnored(uri)) {
return;
}
idle.set(false);
long count = counter.getAndIncrement();
if (count == 0) {
handler.removeCallbacks(transitionToIdle);
}
if (debug) {
Log.i(TAG, "Resource " + resourceName + " counter increased to " + (count + 1));
}
}
/**
* Called when a request is completed (ie the response is returned).
*
* If the URI is not blacklisted the idle counter is decremented. Once the idle counter reaches
* 0, the idle update thread will set the resource as idle after the appropriate timeout.
*/
public void endLoad(String uri) {
if (uriIsIgnored(uri)) {
return;
}
int count = counter.decrementAndGet();
if (count < 0) {
throw new IllegalStateException("Counter has been corrupted! Count=" + count);
} else if (count == 0) {
handler.postDelayed(transitionToIdle, timeoutMs);
}
if (debug) {
Log.i(TAG, "Resource " + resourceName + " counter decreased to " + count);
}
}
private boolean uriIsIgnored(String uri) {
for (Pattern pattern : ignoredRegexes) {
if (pattern.matcher(uri).matches()) {
Log.i(TAG, "Resource " + resourceName + " ignored URI: <" + uri + ">");
return true;
}
}
return false;
}
@VisibleForTesting
void forceIdleTransition() {
transitionToIdle.run();
}
/**
* Wraps a Handler object.
*
* Mock this for testing purposes.
*/
public static interface HandlerIntf {
public void postDelayed(Runnable runnable, long millis);
public void removeCallbacks(Runnable runnable);
}
private static final class DefaultHandler implements HandlerIntf {
private final Handler handler;
public DefaultHandler(Handler handler) {
this.handler = handler;
}
@Override
public void postDelayed(Runnable runnable, long millis) {
handler.postDelayed(runnable, millis);
}
@Override
public void removeCallbacks(Runnable runnable) {
handler.removeCallbacks(runnable);
}
}
}