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