LooperIdlingResourceInterrogationHandler.java
/*
* 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 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 androidx.test.espresso.base;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.MessageQueue;
import androidx.test.espresso.IdlingResource;
import java.util.Locale;
import java.util.concurrent.ConcurrentHashMap;
/** An InterrogationHandler which determines whether a looper is in an idle state. */
class LooperIdlingResourceInterrogationHandler
implements Interrogator.InterrogationHandler<Void>, IdlingResource {
private static final ConcurrentHashMap<String, LooperIdlingResourceInterrogationHandler> insts =
new ConcurrentHashMap<>();
private final Interrogator.QueueInterrogationHandler<Boolean> queueHasNewTasks =
new Interrogator.QueueInterrogationHandler<Boolean>() {
private Boolean hasTasks = Boolean.FALSE;
@Override
public Boolean get() {
return hasTasks;
}
@Override
public boolean queueEmpty() {
hasTasks = Boolean.FALSE;
return false;
}
@Override
public boolean taskDueLong() {
// far in the future, don't care.
hasTasks = Boolean.FALSE;
return false;
}
@Override
public boolean taskDueSoon() {
hasTasks = Boolean.TRUE;
return false;
}
@Override
public boolean barrierUp() {
hasTasks = Boolean.TRUE;
return false;
}
};
private final String name;
// read on main - written on looper
private volatile boolean started = false;
private volatile MessageQueue queue = null;
private volatile boolean idle = true;
// written on main - read on looper
private volatile IdlingResource.ResourceCallback cb = null;
private LooperIdlingResourceInterrogationHandler(String name) {
this.name = name;
}
static LooperIdlingResourceInterrogationHandler forLooper(Looper l) {
String name =
String.format(
Locale.ROOT,
"LooperIdlingResource-%s-%s",
l.getThread().getId(),
l.getThread().getName());
final LooperIdlingResourceInterrogationHandler ir =
new LooperIdlingResourceInterrogationHandler(name);
LooperIdlingResourceInterrogationHandler previous = insts.putIfAbsent(name, ir);
if (null != previous) {
return previous;
}
new Handler(l)
.post(
new Runnable() {
@Override
public void run() {
ir.queue = Looper.myQueue();
ir.started = true;
Interrogator.loopAndInterrogate(ir);
}
});
return ir;
}
@Override
public void setMessage(Message m) {}
@Override
public String getMessage() {
return null;
}
@Override
public void quitting() {
transitionToIdle();
}
@Override
public boolean queueEmpty() {
transitionToIdle();
return true;
}
@Override
public boolean taskDueLong() {
transitionToIdle();
return true;
}
@Override
public boolean beforeTaskDispatch() {
idle = false;
return true;
}
@Override
public boolean taskDueSoon() {
idle = false;
return true;
}
@Override
public boolean barrierUp() {
idle = false;
return true;
}
@Override
public Void get() {
return null;
}
@Override
public boolean isIdleNow() {
if (!started) {
return false;
}
if (idle) {
// make sure nothing has arrived in the queue while the looper thread is waiting to pull a
// new task out of it. There can be some delay between a new message entering the queue and
// the looper thread pulling it out and processing it.
return Boolean.FALSE.equals(Interrogator.peekAtQueueState(queue, queueHasNewTasks));
}
return false;
}
@Override
public String getName() {
return name;
}
@Override
public void registerIdleTransitionCallback(IdlingResource.ResourceCallback cb) {
this.cb = cb;
}
private void transitionToIdle() {
idle = true;
if (null != cb) {
cb.onTransitionToIdle();
}
}
}