Merge "Create frameworks-base-testutils lib" into nyc-mr1-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
d8cdb3ef4b
30
tests/utils/testutils/Android.mk
Normal file
30
tests/utils/testutils/Android.mk
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
#
|
||||||
|
# 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.
|
||||||
|
#
|
||||||
|
|
||||||
|
LOCAL_PATH := $(call my-dir)
|
||||||
|
|
||||||
|
include $(CLEAR_VARS)
|
||||||
|
|
||||||
|
LOCAL_MODULE := frameworks-base-testutils
|
||||||
|
LOCAL_MODULE_TAG := tests
|
||||||
|
|
||||||
|
LOCAL_SRC_FILES := $(call all-java-files-under,java)
|
||||||
|
|
||||||
|
LOCAL_STATIC_JAVA_LIBRARIES := \
|
||||||
|
android-support-test \
|
||||||
|
mockito-target
|
||||||
|
|
||||||
|
include $(BUILD_STATIC_JAVA_LIBRARY)
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
/*
|
||||||
|
* 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 android.app.test;
|
||||||
|
|
||||||
|
import org.mockito.invocation.InvocationOnMock;
|
||||||
|
import org.mockito.stubbing.Answer;
|
||||||
|
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utilities for creating Answers for mock objects
|
||||||
|
*/
|
||||||
|
public class MockAnswerUtil {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Answer that calls the method in the Answer called "answer" that matches the type signature of
|
||||||
|
* the method being answered. An error will be thrown at runtime if the signature does not match
|
||||||
|
* exactly.
|
||||||
|
*/
|
||||||
|
public static class AnswerWithArguments implements Answer<Object> {
|
||||||
|
@Override
|
||||||
|
public final Object answer(InvocationOnMock invocation) throws Throwable {
|
||||||
|
Method method = invocation.getMethod();
|
||||||
|
try {
|
||||||
|
Method implementation = getClass().getMethod("answer", method.getParameterTypes());
|
||||||
|
if (!implementation.getReturnType().equals(method.getReturnType())) {
|
||||||
|
throw new RuntimeException("Found answer method does not have expected return "
|
||||||
|
+ "type. Expected: " + method.getReturnType() + ", got "
|
||||||
|
+ implementation.getReturnType());
|
||||||
|
}
|
||||||
|
Object[] args = invocation.getArguments();
|
||||||
|
try {
|
||||||
|
return implementation.invoke(this, args);
|
||||||
|
} catch (IllegalAccessException e) {
|
||||||
|
throw new RuntimeException("Error invoking answer method", e);
|
||||||
|
} catch (InvocationTargetException e) {
|
||||||
|
throw e.getCause();
|
||||||
|
}
|
||||||
|
} catch (NoSuchMethodException e) {
|
||||||
|
throw new RuntimeException("Could not find answer method with the expected args "
|
||||||
|
+ Arrays.toString(method.getParameterTypes()), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,188 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2015 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 android.app.test;
|
||||||
|
|
||||||
|
import static org.mockito.Mockito.any;
|
||||||
|
import static org.mockito.Mockito.anyInt;
|
||||||
|
import static org.mockito.Mockito.anyLong;
|
||||||
|
import static org.mockito.Mockito.anyString;
|
||||||
|
import static org.mockito.Mockito.doAnswer;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
|
||||||
|
import android.app.AlarmManager;
|
||||||
|
import android.app.test.MockAnswerUtil.AnswerWithArguments;
|
||||||
|
import android.os.Handler;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an AlarmManager whose alarm dispatch can be controlled
|
||||||
|
* Currently only supports alarm listeners
|
||||||
|
*
|
||||||
|
* Alarm listeners will be dispatched to the handler provided or will
|
||||||
|
* be dispatched immediately if they would have been sent to the main
|
||||||
|
* looper (handler was null).
|
||||||
|
*/
|
||||||
|
public class TestAlarmManager {
|
||||||
|
private final AlarmManager mAlarmManager;
|
||||||
|
private final List<PendingAlarm> mPendingAlarms;
|
||||||
|
|
||||||
|
public TestAlarmManager() throws Exception {
|
||||||
|
mPendingAlarms = new ArrayList<>();
|
||||||
|
|
||||||
|
mAlarmManager = mock(AlarmManager.class);
|
||||||
|
doAnswer(new SetListenerAnswer()).when(mAlarmManager).set(anyInt(), anyLong(), anyString(),
|
||||||
|
any(AlarmManager.OnAlarmListener.class), any(Handler.class));
|
||||||
|
doAnswer(new SetListenerAnswer()).when(mAlarmManager).setExact(anyInt(), anyLong(),
|
||||||
|
anyString(), any(AlarmManager.OnAlarmListener.class), any(Handler.class));
|
||||||
|
doAnswer(new CancelListenerAnswer())
|
||||||
|
.when(mAlarmManager).cancel(any(AlarmManager.OnAlarmListener.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
public AlarmManager getAlarmManager() {
|
||||||
|
return mAlarmManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dispatch a pending alarm with the given tag
|
||||||
|
* @return if any alarm was dispatched
|
||||||
|
*/
|
||||||
|
public boolean dispatch(String tag) {
|
||||||
|
for (int i = 0; i < mPendingAlarms.size(); ++i) {
|
||||||
|
PendingAlarm alarm = mPendingAlarms.get(i);
|
||||||
|
if (Objects.equals(tag, alarm.getTag())) {
|
||||||
|
mPendingAlarms.remove(i);
|
||||||
|
alarm.dispatch();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return if an alarm with the given tag is pending
|
||||||
|
*/
|
||||||
|
public boolean isPending(String tag) {
|
||||||
|
for (int i = 0; i < mPendingAlarms.size(); ++i) {
|
||||||
|
PendingAlarm alarm = mPendingAlarms.get(i);
|
||||||
|
if (Objects.equals(tag, alarm.getTag())) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return trigger time of an pending alarm with the given tag
|
||||||
|
* -1 if no pending alarm with the given tag
|
||||||
|
*/
|
||||||
|
public long getTriggerTimeMillis(String tag) {
|
||||||
|
for (int i = 0; i < mPendingAlarms.size(); ++i) {
|
||||||
|
PendingAlarm alarm = mPendingAlarms.get(i);
|
||||||
|
if (Objects.equals(tag, alarm.getTag())) {
|
||||||
|
return alarm.getTriggerTimeMillis();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class PendingAlarm {
|
||||||
|
private final int mType;
|
||||||
|
private final long mTriggerAtMillis;
|
||||||
|
private final String mTag;
|
||||||
|
private final Runnable mCallback;
|
||||||
|
|
||||||
|
public PendingAlarm(int type, long triggerAtMillis, String tag, Runnable callback) {
|
||||||
|
mType = type;
|
||||||
|
mTriggerAtMillis = triggerAtMillis;
|
||||||
|
mTag = tag;
|
||||||
|
mCallback = callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void dispatch() {
|
||||||
|
if (mCallback != null) {
|
||||||
|
mCallback.run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Runnable getCallback() {
|
||||||
|
return mCallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTag() {
|
||||||
|
return mTag;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getTriggerTimeMillis() {
|
||||||
|
return mTriggerAtMillis;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class SetListenerAnswer extends AnswerWithArguments {
|
||||||
|
public void answer(int type, long triggerAtMillis, String tag,
|
||||||
|
AlarmManager.OnAlarmListener listener, Handler handler) {
|
||||||
|
mPendingAlarms.add(new PendingAlarm(type, triggerAtMillis, tag,
|
||||||
|
new AlarmListenerRunnable(listener, handler)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class CancelListenerAnswer extends AnswerWithArguments {
|
||||||
|
public void answer(AlarmManager.OnAlarmListener listener) {
|
||||||
|
Iterator<PendingAlarm> alarmItr = mPendingAlarms.iterator();
|
||||||
|
while (alarmItr.hasNext()) {
|
||||||
|
PendingAlarm alarm = alarmItr.next();
|
||||||
|
if (alarm.getCallback() instanceof AlarmListenerRunnable) {
|
||||||
|
AlarmListenerRunnable alarmCallback =
|
||||||
|
(AlarmListenerRunnable) alarm.getCallback();
|
||||||
|
if (alarmCallback.getListener() == listener) {
|
||||||
|
alarmItr.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class AlarmListenerRunnable implements Runnable {
|
||||||
|
private final AlarmManager.OnAlarmListener mListener;
|
||||||
|
private final Handler mHandler;
|
||||||
|
public AlarmListenerRunnable(AlarmManager.OnAlarmListener listener, Handler handler) {
|
||||||
|
mListener = listener;
|
||||||
|
mHandler = handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AlarmManager.OnAlarmListener getListener() {
|
||||||
|
return mListener;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
if (mHandler != null) {
|
||||||
|
mHandler.post(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
mListener.onAlarm();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else { // normally gets dispatched in main looper
|
||||||
|
mListener.onAlarm();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
283
tests/utils/testutils/java/android/os/test/TestLooper.java
Normal file
283
tests/utils/testutils/java/android/os/test/TestLooper.java
Normal file
@@ -0,0 +1,283 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2015 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 android.os.test;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
import android.os.Looper;
|
||||||
|
import android.os.Message;
|
||||||
|
import android.os.MessageQueue;
|
||||||
|
import android.os.SystemClock;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import java.lang.reflect.Constructor;
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a looper whose message queue can be manipulated
|
||||||
|
* This allows testing code that uses a looper to dispatch messages in a deterministic manner
|
||||||
|
* Creating a TestLooper will also install it as the looper for the current thread
|
||||||
|
*/
|
||||||
|
public class TestLooper {
|
||||||
|
protected final Looper mLooper;
|
||||||
|
|
||||||
|
private static final Constructor<Looper> LOOPER_CONSTRUCTOR;
|
||||||
|
private static final Field THREAD_LOCAL_LOOPER_FIELD;
|
||||||
|
private static final Field MESSAGE_QUEUE_MESSAGES_FIELD;
|
||||||
|
private static final Field MESSAGE_NEXT_FIELD;
|
||||||
|
private static final Field MESSAGE_WHEN_FIELD;
|
||||||
|
private static final Method MESSAGE_MARK_IN_USE_METHOD;
|
||||||
|
private static final String TAG = "TestLooper";
|
||||||
|
|
||||||
|
private AutoDispatchThread mAutoDispatchThread;
|
||||||
|
|
||||||
|
static {
|
||||||
|
try {
|
||||||
|
LOOPER_CONSTRUCTOR = Looper.class.getDeclaredConstructor(Boolean.TYPE);
|
||||||
|
LOOPER_CONSTRUCTOR.setAccessible(true);
|
||||||
|
THREAD_LOCAL_LOOPER_FIELD = Looper.class.getDeclaredField("sThreadLocal");
|
||||||
|
THREAD_LOCAL_LOOPER_FIELD.setAccessible(true);
|
||||||
|
MESSAGE_QUEUE_MESSAGES_FIELD = MessageQueue.class.getDeclaredField("mMessages");
|
||||||
|
MESSAGE_QUEUE_MESSAGES_FIELD.setAccessible(true);
|
||||||
|
MESSAGE_NEXT_FIELD = Message.class.getDeclaredField("next");
|
||||||
|
MESSAGE_NEXT_FIELD.setAccessible(true);
|
||||||
|
MESSAGE_WHEN_FIELD = Message.class.getDeclaredField("when");
|
||||||
|
MESSAGE_WHEN_FIELD.setAccessible(true);
|
||||||
|
MESSAGE_MARK_IN_USE_METHOD = Message.class.getDeclaredMethod("markInUse");
|
||||||
|
MESSAGE_MARK_IN_USE_METHOD.setAccessible(true);
|
||||||
|
} catch (NoSuchFieldException | NoSuchMethodException e) {
|
||||||
|
throw new RuntimeException("Failed to initialize TestLooper", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public TestLooper() {
|
||||||
|
try {
|
||||||
|
mLooper = LOOPER_CONSTRUCTOR.newInstance(false);
|
||||||
|
|
||||||
|
ThreadLocal<Looper> threadLocalLooper = (ThreadLocal<Looper>) THREAD_LOCAL_LOOPER_FIELD
|
||||||
|
.get(null);
|
||||||
|
threadLocalLooper.set(mLooper);
|
||||||
|
} catch (IllegalAccessException | InstantiationException | InvocationTargetException e) {
|
||||||
|
throw new RuntimeException("Reflection error constructing or accessing looper", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Looper getLooper() {
|
||||||
|
return mLooper;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Message getMessageLinkedList() {
|
||||||
|
try {
|
||||||
|
MessageQueue queue = mLooper.getQueue();
|
||||||
|
return (Message) MESSAGE_QUEUE_MESSAGES_FIELD.get(queue);
|
||||||
|
} catch (IllegalAccessException e) {
|
||||||
|
throw new RuntimeException("Access failed in TestLooper: get - MessageQueue.mMessages",
|
||||||
|
e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void moveTimeForward(long milliSeconds) {
|
||||||
|
try {
|
||||||
|
Message msg = getMessageLinkedList();
|
||||||
|
while (msg != null) {
|
||||||
|
long updatedWhen = msg.getWhen() - milliSeconds;
|
||||||
|
if (updatedWhen < 0) {
|
||||||
|
updatedWhen = 0;
|
||||||
|
}
|
||||||
|
MESSAGE_WHEN_FIELD.set(msg, updatedWhen);
|
||||||
|
msg = (Message) MESSAGE_NEXT_FIELD.get(msg);
|
||||||
|
}
|
||||||
|
} catch (IllegalAccessException e) {
|
||||||
|
throw new RuntimeException("Access failed in TestLooper: set - Message.when", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Message messageQueueNext() {
|
||||||
|
try {
|
||||||
|
long now = SystemClock.uptimeMillis();
|
||||||
|
|
||||||
|
Message prevMsg = null;
|
||||||
|
Message msg = getMessageLinkedList();
|
||||||
|
if (msg != null && msg.getTarget() == null) {
|
||||||
|
// Stalled by a barrier. Find the next asynchronous message in
|
||||||
|
// the queue.
|
||||||
|
do {
|
||||||
|
prevMsg = msg;
|
||||||
|
msg = (Message) MESSAGE_NEXT_FIELD.get(msg);
|
||||||
|
} while (msg != null && !msg.isAsynchronous());
|
||||||
|
}
|
||||||
|
if (msg != null) {
|
||||||
|
if (now >= msg.getWhen()) {
|
||||||
|
// Got a message.
|
||||||
|
if (prevMsg != null) {
|
||||||
|
MESSAGE_NEXT_FIELD.set(prevMsg, MESSAGE_NEXT_FIELD.get(msg));
|
||||||
|
} else {
|
||||||
|
MESSAGE_QUEUE_MESSAGES_FIELD.set(mLooper.getQueue(),
|
||||||
|
MESSAGE_NEXT_FIELD.get(msg));
|
||||||
|
}
|
||||||
|
MESSAGE_NEXT_FIELD.set(msg, null);
|
||||||
|
MESSAGE_MARK_IN_USE_METHOD.invoke(msg);
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (IllegalAccessException | InvocationTargetException e) {
|
||||||
|
throw new RuntimeException("Access failed in TestLooper", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return true if there are pending messages in the message queue
|
||||||
|
*/
|
||||||
|
public synchronized boolean isIdle() {
|
||||||
|
Message messageList = getMessageLinkedList();
|
||||||
|
|
||||||
|
return messageList != null && SystemClock.uptimeMillis() >= messageList.getWhen();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the next message in the Looper's message queue or null if there is none
|
||||||
|
*/
|
||||||
|
public synchronized Message nextMessage() {
|
||||||
|
if (isIdle()) {
|
||||||
|
return messageQueueNext();
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dispatch the next message in the queue
|
||||||
|
* Asserts that there is a message in the queue
|
||||||
|
*/
|
||||||
|
public synchronized void dispatchNext() {
|
||||||
|
assertTrue(isIdle());
|
||||||
|
Message msg = messageQueueNext();
|
||||||
|
if (msg == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
msg.getTarget().dispatchMessage(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dispatch all messages currently in the queue
|
||||||
|
* Will not fail if there are no messages pending
|
||||||
|
* @return the number of messages dispatched
|
||||||
|
*/
|
||||||
|
public synchronized int dispatchAll() {
|
||||||
|
int count = 0;
|
||||||
|
while (isIdle()) {
|
||||||
|
dispatchNext();
|
||||||
|
++count;
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thread used to dispatch messages when the main thread is blocked waiting for a response.
|
||||||
|
*/
|
||||||
|
private class AutoDispatchThread extends Thread {
|
||||||
|
private static final int MAX_LOOPS = 100;
|
||||||
|
private static final int LOOP_SLEEP_TIME_MS = 10;
|
||||||
|
|
||||||
|
private RuntimeException mAutoDispatchException = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run method for the auto dispatch thread.
|
||||||
|
* The thread loops a maximum of MAX_LOOPS times with a 10ms sleep between loops.
|
||||||
|
* The thread continues looping and attempting to dispatch all messages until at
|
||||||
|
* least one message has been dispatched.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
int dispatchCount = 0;
|
||||||
|
for (int i = 0; i < MAX_LOOPS; i++) {
|
||||||
|
try {
|
||||||
|
dispatchCount = dispatchAll();
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
mAutoDispatchException = e;
|
||||||
|
}
|
||||||
|
Log.d(TAG, "dispatched " + dispatchCount + " messages");
|
||||||
|
if (dispatchCount > 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Thread.sleep(LOOP_SLEEP_TIME_MS);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
mAutoDispatchException = new IllegalStateException(
|
||||||
|
"stopAutoDispatch called before any messages were dispatched.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Log.e(TAG, "AutoDispatchThread did not dispatch any messages.");
|
||||||
|
mAutoDispatchException = new IllegalStateException(
|
||||||
|
"TestLooper did not dispatch any messages before exiting.");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method allowing the TestLooper to pass any exceptions thrown by the thread to be passed
|
||||||
|
* to the main thread.
|
||||||
|
*
|
||||||
|
* @return RuntimeException Exception created by stopping without dispatching a message
|
||||||
|
*/
|
||||||
|
public RuntimeException getException() {
|
||||||
|
return mAutoDispatchException;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create and start a new AutoDispatchThread if one is not already running.
|
||||||
|
*/
|
||||||
|
public void startAutoDispatch() {
|
||||||
|
if (mAutoDispatchThread != null) {
|
||||||
|
throw new IllegalStateException(
|
||||||
|
"startAutoDispatch called with the AutoDispatchThread already running.");
|
||||||
|
}
|
||||||
|
mAutoDispatchThread = new AutoDispatchThread();
|
||||||
|
mAutoDispatchThread.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If an AutoDispatchThread is currently running, stop and clean up.
|
||||||
|
*/
|
||||||
|
public void stopAutoDispatch() {
|
||||||
|
if (mAutoDispatchThread != null) {
|
||||||
|
if (mAutoDispatchThread.isAlive()) {
|
||||||
|
mAutoDispatchThread.interrupt();
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
mAutoDispatchThread.join();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
// Catch exception from join.
|
||||||
|
}
|
||||||
|
|
||||||
|
RuntimeException e = mAutoDispatchThread.getException();
|
||||||
|
mAutoDispatchThread = null;
|
||||||
|
if (e != null) {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// stopAutoDispatch was called when startAutoDispatch has not created a new thread.
|
||||||
|
throw new IllegalStateException(
|
||||||
|
"stopAutoDispatch called without startAutoDispatch.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
371
tests/utils/testutils/java/android/os/test/TestLooperTest.java
Normal file
371
tests/utils/testutils/java/android/os/test/TestLooperTest.java
Normal file
@@ -0,0 +1,371 @@
|
|||||||
|
/*
|
||||||
|
* 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 android.os.test;
|
||||||
|
|
||||||
|
import static org.hamcrest.core.IsEqual.equalTo;
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
import static org.mockito.Matchers.any;
|
||||||
|
import static org.mockito.Mockito.inOrder;
|
||||||
|
import static org.mockito.Mockito.never;
|
||||||
|
import static org.mockito.Mockito.spy;
|
||||||
|
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Looper;
|
||||||
|
import android.os.Message;
|
||||||
|
import android.test.suitebuilder.annotation.SmallTest;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.rules.ErrorCollector;
|
||||||
|
import org.mockito.ArgumentCaptor;
|
||||||
|
import org.mockito.InOrder;
|
||||||
|
import org.mockito.MockitoAnnotations;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test TestLooperAbstractTime which provides control over "time". Note that
|
||||||
|
* real-time is being used as well. Therefore small time increments are NOT
|
||||||
|
* reliable. All tests are in "K" units (i.e. *1000).
|
||||||
|
*/
|
||||||
|
|
||||||
|
@SmallTest
|
||||||
|
public class TestLooperTest {
|
||||||
|
private TestLooper mTestLooper;
|
||||||
|
private Handler mHandler;
|
||||||
|
private Handler mHandlerSpy;
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public ErrorCollector collector = new ErrorCollector();
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() throws Exception {
|
||||||
|
MockitoAnnotations.initMocks(this);
|
||||||
|
|
||||||
|
mTestLooper = new TestLooper();
|
||||||
|
mHandler = new Handler(mTestLooper.getLooper());
|
||||||
|
mHandlerSpy = spy(mHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Basic test with no time stamps: dispatch 4 messages, check that all 4
|
||||||
|
* delivered (in correct order).
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testNoTimeMovement() {
|
||||||
|
final int messageA = 1;
|
||||||
|
final int messageB = 2;
|
||||||
|
final int messageC = 3;
|
||||||
|
|
||||||
|
InOrder inOrder = inOrder(mHandlerSpy);
|
||||||
|
ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
|
||||||
|
|
||||||
|
mHandlerSpy.sendMessage(mHandler.obtainMessage(messageA));
|
||||||
|
mHandlerSpy.sendMessage(mHandler.obtainMessage(messageA));
|
||||||
|
mHandlerSpy.sendMessage(mHandler.obtainMessage(messageB));
|
||||||
|
mHandlerSpy.sendMessage(mHandler.obtainMessage(messageC));
|
||||||
|
mTestLooper.dispatchAll();
|
||||||
|
|
||||||
|
inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
|
||||||
|
collector.checkThat("1: messageA", messageA, equalTo(messageCaptor.getValue().what));
|
||||||
|
inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
|
||||||
|
collector.checkThat("2: messageA", messageA, equalTo(messageCaptor.getValue().what));
|
||||||
|
inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
|
||||||
|
collector.checkThat("3: messageB", messageB, equalTo(messageCaptor.getValue().what));
|
||||||
|
inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
|
||||||
|
collector.checkThat("4: messageC", messageC, equalTo(messageCaptor.getValue().what));
|
||||||
|
|
||||||
|
inOrder.verify(mHandlerSpy, never()).handleMessage(any(Message.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test message sequence: A, B, C@5K, A@10K. Don't move time.
|
||||||
|
* <p>
|
||||||
|
* Expected: only get A, B
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testDelayedDispatchNoTimeMove() {
|
||||||
|
final int messageA = 1;
|
||||||
|
final int messageB = 2;
|
||||||
|
final int messageC = 3;
|
||||||
|
|
||||||
|
InOrder inOrder = inOrder(mHandlerSpy);
|
||||||
|
ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
|
||||||
|
|
||||||
|
mHandlerSpy.sendMessage(mHandler.obtainMessage(messageA));
|
||||||
|
mHandlerSpy.sendMessage(mHandler.obtainMessage(messageB));
|
||||||
|
mHandlerSpy.sendMessageDelayed(mHandler.obtainMessage(messageC), 5000);
|
||||||
|
mHandlerSpy.sendMessageDelayed(mHandler.obtainMessage(messageA), 10000);
|
||||||
|
mTestLooper.dispatchAll();
|
||||||
|
|
||||||
|
inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
|
||||||
|
collector.checkThat("1: messageA", messageA, equalTo(messageCaptor.getValue().what));
|
||||||
|
inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
|
||||||
|
collector.checkThat("2: messageB", messageB, equalTo(messageCaptor.getValue().what));
|
||||||
|
|
||||||
|
inOrder.verify(mHandlerSpy, never()).handleMessage(any(Message.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test message sequence: A, B, C@5K, A@10K, Advance time by 5K.
|
||||||
|
* <p>
|
||||||
|
* Expected: only get A, B, C
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testDelayedDispatchAdvanceTimeOnce() {
|
||||||
|
final int messageA = 1;
|
||||||
|
final int messageB = 2;
|
||||||
|
final int messageC = 3;
|
||||||
|
|
||||||
|
InOrder inOrder = inOrder(mHandlerSpy);
|
||||||
|
ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
|
||||||
|
|
||||||
|
mHandlerSpy.sendMessage(mHandler.obtainMessage(messageA));
|
||||||
|
mHandlerSpy.sendMessage(mHandler.obtainMessage(messageB));
|
||||||
|
mHandlerSpy.sendMessageDelayed(mHandler.obtainMessage(messageC), 5000);
|
||||||
|
mHandlerSpy.sendMessageDelayed(mHandler.obtainMessage(messageA), 10000);
|
||||||
|
mTestLooper.moveTimeForward(5000);
|
||||||
|
mTestLooper.dispatchAll();
|
||||||
|
|
||||||
|
inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
|
||||||
|
collector.checkThat("1: messageA", messageA, equalTo(messageCaptor.getValue().what));
|
||||||
|
inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
|
||||||
|
collector.checkThat("2: messageB", messageB, equalTo(messageCaptor.getValue().what));
|
||||||
|
inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
|
||||||
|
collector.checkThat("3: messageC", messageC, equalTo(messageCaptor.getValue().what));
|
||||||
|
|
||||||
|
inOrder.verify(mHandlerSpy, never()).handleMessage(any(Message.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test message sequence: A, B, C@5K, Advance time by 4K, A@1K, B@2K Advance
|
||||||
|
* time by 1K.
|
||||||
|
* <p>
|
||||||
|
* Expected: get A, B, C, A
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testDelayedDispatchAdvanceTimeTwice() {
|
||||||
|
final int messageA = 1;
|
||||||
|
final int messageB = 2;
|
||||||
|
final int messageC = 3;
|
||||||
|
|
||||||
|
InOrder inOrder = inOrder(mHandlerSpy);
|
||||||
|
ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
|
||||||
|
|
||||||
|
mHandlerSpy.sendMessage(mHandler.obtainMessage(messageA));
|
||||||
|
mHandlerSpy.sendMessage(mHandler.obtainMessage(messageB));
|
||||||
|
mHandlerSpy.sendMessageDelayed(mHandler.obtainMessage(messageC), 5000);
|
||||||
|
mTestLooper.moveTimeForward(4000);
|
||||||
|
mHandlerSpy.sendMessageDelayed(mHandler.obtainMessage(messageA), 1000);
|
||||||
|
mHandlerSpy.sendMessageDelayed(mHandler.obtainMessage(messageB), 2000);
|
||||||
|
mTestLooper.moveTimeForward(1000);
|
||||||
|
mTestLooper.dispatchAll();
|
||||||
|
|
||||||
|
inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
|
||||||
|
collector.checkThat("1: messageA", messageA, equalTo(messageCaptor.getValue().what));
|
||||||
|
inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
|
||||||
|
collector.checkThat("2: messageB", messageB, equalTo(messageCaptor.getValue().what));
|
||||||
|
inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
|
||||||
|
collector.checkThat("3: messageC", messageC, equalTo(messageCaptor.getValue().what));
|
||||||
|
inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
|
||||||
|
collector.checkThat("4: messageA", messageA, equalTo(messageCaptor.getValue().what));
|
||||||
|
|
||||||
|
inOrder.verify(mHandlerSpy, never()).handleMessage(any(Message.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test message sequence: A, B, C@5K, Advance time by 4K, A@5K, B@2K Advance
|
||||||
|
* time by 3K.
|
||||||
|
* <p>
|
||||||
|
* Expected: get A, B, C, B
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testDelayedDispatchReverseOrder() {
|
||||||
|
final int messageA = 1;
|
||||||
|
final int messageB = 2;
|
||||||
|
final int messageC = 3;
|
||||||
|
|
||||||
|
InOrder inOrder = inOrder(mHandlerSpy);
|
||||||
|
ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
|
||||||
|
|
||||||
|
mHandlerSpy.sendMessage(mHandler.obtainMessage(messageA));
|
||||||
|
mHandlerSpy.sendMessage(mHandler.obtainMessage(messageB));
|
||||||
|
mHandlerSpy.sendMessageDelayed(mHandler.obtainMessage(messageC), 5000);
|
||||||
|
mTestLooper.moveTimeForward(4000);
|
||||||
|
mHandlerSpy.sendMessageDelayed(mHandler.obtainMessage(messageA), 5000);
|
||||||
|
mHandlerSpy.sendMessageDelayed(mHandler.obtainMessage(messageB), 2000);
|
||||||
|
mTestLooper.moveTimeForward(3000);
|
||||||
|
mTestLooper.dispatchAll();
|
||||||
|
|
||||||
|
inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
|
||||||
|
collector.checkThat("1: messageA", messageA, equalTo(messageCaptor.getValue().what));
|
||||||
|
inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
|
||||||
|
collector.checkThat("2: messageB", messageB, equalTo(messageCaptor.getValue().what));
|
||||||
|
inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
|
||||||
|
collector.checkThat("3: messageC", messageC, equalTo(messageCaptor.getValue().what));
|
||||||
|
inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
|
||||||
|
collector.checkThat("4: messageB", messageB, equalTo(messageCaptor.getValue().what));
|
||||||
|
|
||||||
|
inOrder.verify(mHandlerSpy, never()).handleMessage(any(Message.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test message sequence: A, B, C@5K, Advance time by 4K, dispatch all,
|
||||||
|
* A@5K, B@2K Advance time by 3K, dispatch all.
|
||||||
|
* <p>
|
||||||
|
* Expected: get A, B after first dispatch; then C, B after second dispatch
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testDelayedDispatchAllMultipleTimes() {
|
||||||
|
final int messageA = 1;
|
||||||
|
final int messageB = 2;
|
||||||
|
final int messageC = 3;
|
||||||
|
|
||||||
|
InOrder inOrder = inOrder(mHandlerSpy);
|
||||||
|
ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
|
||||||
|
|
||||||
|
mHandlerSpy.sendMessage(mHandler.obtainMessage(messageA));
|
||||||
|
mHandlerSpy.sendMessage(mHandler.obtainMessage(messageB));
|
||||||
|
mHandlerSpy.sendMessageDelayed(mHandler.obtainMessage(messageC), 5000);
|
||||||
|
mTestLooper.moveTimeForward(4000);
|
||||||
|
mTestLooper.dispatchAll();
|
||||||
|
|
||||||
|
inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
|
||||||
|
collector.checkThat("1: messageA", messageA, equalTo(messageCaptor.getValue().what));
|
||||||
|
inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
|
||||||
|
collector.checkThat("2: messageB", messageB, equalTo(messageCaptor.getValue().what));
|
||||||
|
|
||||||
|
mHandlerSpy.sendMessageDelayed(mHandler.obtainMessage(messageA), 5000);
|
||||||
|
mHandlerSpy.sendMessageDelayed(mHandler.obtainMessage(messageB), 2000);
|
||||||
|
mTestLooper.moveTimeForward(3000);
|
||||||
|
mTestLooper.dispatchAll();
|
||||||
|
|
||||||
|
inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
|
||||||
|
collector.checkThat("3: messageC", messageC, equalTo(messageCaptor.getValue().what));
|
||||||
|
inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
|
||||||
|
collector.checkThat("4: messageB", messageB, equalTo(messageCaptor.getValue().what));
|
||||||
|
|
||||||
|
inOrder.verify(mHandlerSpy, never()).handleMessage(any(Message.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test AutoDispatch for a single message.
|
||||||
|
* This test would ideally use the Channel sendMessageSynchronously. At this time, the setup to
|
||||||
|
* get a working test channel is cumbersome. Until this is fixed, we substitute with a
|
||||||
|
* sendMessage followed by a blocking call. The main test thread blocks until the test handler
|
||||||
|
* receives the test message (messageA) and sets a boolean true. Once the boolean is true, the
|
||||||
|
* main thread will exit the busy wait loop, stop autoDispatch and check the assert.
|
||||||
|
*
|
||||||
|
* Enable AutoDispatch, add message, block on message being handled and stop AutoDispatch.
|
||||||
|
* <p>
|
||||||
|
* Expected: handleMessage is called for messageA and stopAutoDispatch is called.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testAutoDispatchWithSingleMessage() {
|
||||||
|
final int mLoopSleepTimeMs = 5;
|
||||||
|
|
||||||
|
final int messageA = 1;
|
||||||
|
|
||||||
|
TestLooper mockLooper = new TestLooper();
|
||||||
|
class TestHandler extends Handler {
|
||||||
|
public volatile boolean handledMessage = false;
|
||||||
|
TestHandler(Looper looper) {
|
||||||
|
super(looper);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleMessage(Message msg) {
|
||||||
|
if (msg.what == messageA) {
|
||||||
|
handledMessage = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TestHandler testHandler = new TestHandler(mockLooper.getLooper());
|
||||||
|
mockLooper.startAutoDispatch();
|
||||||
|
testHandler.sendMessage(testHandler.obtainMessage(messageA));
|
||||||
|
while (!testHandler.handledMessage) {
|
||||||
|
// Block until message is handled
|
||||||
|
try {
|
||||||
|
Thread.sleep(mLoopSleepTimeMs);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
// Interrupted while sleeping.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mockLooper.stopAutoDispatch();
|
||||||
|
assertTrue("TestHandler should have received messageA", testHandler.handledMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test starting AutoDispatch while already running throws IllegalStateException
|
||||||
|
* Enable AutoDispatch two times in a row.
|
||||||
|
* <p>
|
||||||
|
* Expected: catch IllegalStateException on second call.
|
||||||
|
*/
|
||||||
|
@Test(expected = IllegalStateException.class)
|
||||||
|
public void testRepeatedStartAutoDispatchThrowsException() {
|
||||||
|
mTestLooper.startAutoDispatch();
|
||||||
|
mTestLooper.startAutoDispatch();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test stopping AutoDispatch without previously starting throws IllegalStateException
|
||||||
|
* Stop AutoDispatch
|
||||||
|
* <p>
|
||||||
|
* Expected: catch IllegalStateException on second call.
|
||||||
|
*/
|
||||||
|
@Test(expected = IllegalStateException.class)
|
||||||
|
public void testStopAutoDispatchWithoutStartThrowsException() {
|
||||||
|
mTestLooper.stopAutoDispatch();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test AutoDispatch exits and does not dispatch a later message.
|
||||||
|
* Start and stop AutoDispatch then add a message.
|
||||||
|
* <p>
|
||||||
|
* Expected: After AutoDispatch is stopped, dispatchAll will return 1.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testAutoDispatchStopsCleanlyWithoutDispatchingAMessage() {
|
||||||
|
final int messageA = 1;
|
||||||
|
|
||||||
|
InOrder inOrder = inOrder(mHandlerSpy);
|
||||||
|
ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
|
||||||
|
|
||||||
|
mTestLooper.startAutoDispatch();
|
||||||
|
try {
|
||||||
|
mTestLooper.stopAutoDispatch();
|
||||||
|
} catch (IllegalStateException e) {
|
||||||
|
// Stopping without a dispatch will throw an exception.
|
||||||
|
}
|
||||||
|
|
||||||
|
mHandlerSpy.sendMessage(mHandler.obtainMessage(messageA));
|
||||||
|
assertEquals("One message should be dispatched", 1, mTestLooper.dispatchAll());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test AutoDispatch throws an exception when no messages are dispatched.
|
||||||
|
* Start and stop AutoDispatch
|
||||||
|
* <p>
|
||||||
|
* Expected: Exception is thrown with the stopAutoDispatch call.
|
||||||
|
*/
|
||||||
|
@Test(expected = IllegalStateException.class)
|
||||||
|
public void testAutoDispatchThrowsExceptionWhenNoMessagesDispatched() {
|
||||||
|
mTestLooper.startAutoDispatch();
|
||||||
|
mTestLooper.stopAutoDispatch();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
/*
|
||||||
|
* 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 com.android.internal.util.test;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Looper;
|
||||||
|
import android.os.Message;
|
||||||
|
import android.os.Messenger;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import com.android.internal.util.AsyncChannel;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides an AsyncChannel interface that implements the connection initiating half of a
|
||||||
|
* bidirectional channel as described in {@link com.android.internal.util.AsyncChannel}.
|
||||||
|
*/
|
||||||
|
public class BidirectionalAsyncChannel {
|
||||||
|
private static final String TAG = "BidirectionalAsyncChannel";
|
||||||
|
|
||||||
|
private AsyncChannel mChannel;
|
||||||
|
public enum ChannelState { DISCONNECTED, HALF_CONNECTED, CONNECTED, FAILURE };
|
||||||
|
private ChannelState mState = ChannelState.DISCONNECTED;
|
||||||
|
|
||||||
|
public void assertConnected() {
|
||||||
|
assertEquals("AsyncChannel was not fully connected", ChannelState.CONNECTED, mState);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void connect(final Looper looper, final Messenger messenger,
|
||||||
|
final Handler incomingMessageHandler) {
|
||||||
|
assertEquals("AsyncChannel must be disconnected to connect",
|
||||||
|
ChannelState.DISCONNECTED, mState);
|
||||||
|
mChannel = new AsyncChannel();
|
||||||
|
Handler rawMessageHandler = new Handler(looper) {
|
||||||
|
@Override
|
||||||
|
public void handleMessage(Message msg) {
|
||||||
|
switch (msg.what) {
|
||||||
|
case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED:
|
||||||
|
if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) {
|
||||||
|
Log.d(TAG, "Successfully half connected " + this);
|
||||||
|
mChannel.sendMessage(AsyncChannel.CMD_CHANNEL_FULL_CONNECTION);
|
||||||
|
mState = ChannelState.HALF_CONNECTED;
|
||||||
|
} else {
|
||||||
|
Log.d(TAG, "Failed to connect channel " + this);
|
||||||
|
mState = ChannelState.FAILURE;
|
||||||
|
mChannel = null;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED:
|
||||||
|
mState = ChannelState.CONNECTED;
|
||||||
|
Log.d(TAG, "Channel fully connected" + this);
|
||||||
|
break;
|
||||||
|
case AsyncChannel.CMD_CHANNEL_DISCONNECTED:
|
||||||
|
mState = ChannelState.DISCONNECTED;
|
||||||
|
mChannel = null;
|
||||||
|
Log.d(TAG, "Channel disconnected" + this);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
incomingMessageHandler.handleMessage(msg);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
mChannel.connect(null, rawMessageHandler, messenger);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void disconnect() {
|
||||||
|
assertEquals("AsyncChannel must be connected to disconnect",
|
||||||
|
ChannelState.CONNECTED, mState);
|
||||||
|
mChannel.sendMessage(AsyncChannel.CMD_CHANNEL_DISCONNECT);
|
||||||
|
mState = ChannelState.DISCONNECTED;
|
||||||
|
mChannel = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sendMessage(Message msg) {
|
||||||
|
assertEquals("AsyncChannel must be connected to send messages",
|
||||||
|
ChannelState.CONNECTED, mState);
|
||||||
|
mChannel.sendMessage(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
/*
|
||||||
|
* 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 com.android.internal.util.test;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Looper;
|
||||||
|
import android.os.Message;
|
||||||
|
import android.os.Messenger;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import com.android.internal.util.AsyncChannel;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides an interface for the server side implementation of a bidirectional channel as described
|
||||||
|
* in {@link com.android.internal.util.AsyncChannel}.
|
||||||
|
*/
|
||||||
|
public class BidirectionalAsyncChannelServer {
|
||||||
|
|
||||||
|
private static final String TAG = "BidirectionalAsyncChannelServer";
|
||||||
|
|
||||||
|
// Keeps track of incoming clients, which are identifiable by their messengers.
|
||||||
|
private final Map<Messenger, AsyncChannel> mClients = new HashMap<>();
|
||||||
|
|
||||||
|
private Messenger mMessenger;
|
||||||
|
|
||||||
|
public BidirectionalAsyncChannelServer(final Context context, final Looper looper,
|
||||||
|
final Handler messageHandler) {
|
||||||
|
Handler handler = new Handler(looper) {
|
||||||
|
@Override
|
||||||
|
public void handleMessage(Message msg) {
|
||||||
|
AsyncChannel channel = mClients.get(msg.replyTo);
|
||||||
|
switch (msg.what) {
|
||||||
|
case AsyncChannel.CMD_CHANNEL_FULL_CONNECTION:
|
||||||
|
if (channel != null) {
|
||||||
|
Log.d(TAG, "duplicate client connection: " + msg.sendingUid);
|
||||||
|
channel.replyToMessage(msg,
|
||||||
|
AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED,
|
||||||
|
AsyncChannel.STATUS_FULL_CONNECTION_REFUSED_ALREADY_CONNECTED);
|
||||||
|
} else {
|
||||||
|
channel = new AsyncChannel();
|
||||||
|
mClients.put(msg.replyTo, channel);
|
||||||
|
channel.connected(context, this, msg.replyTo);
|
||||||
|
channel.replyToMessage(msg, AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED,
|
||||||
|
AsyncChannel.STATUS_SUCCESSFUL);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case AsyncChannel.CMD_CHANNEL_DISCONNECT:
|
||||||
|
channel.disconnect();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case AsyncChannel.CMD_CHANNEL_DISCONNECTED:
|
||||||
|
mClients.remove(msg.replyTo);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
messageHandler.handleMessage(msg);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
mMessenger = new Messenger(handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Messenger getMessenger() {
|
||||||
|
return mMessenger;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user