Commit 6a2a4bd5 authored by Pavel Yatsuk's avatar Pavel Yatsuk Committed by Commit Bot

[Messages] Basic implementation of MessageQueueManager

This is a basic implementation of MessageQueueManager.
MessageQueueManager:
- maintains a queue of messages and a mapping from message key objects to
  MessageStateHandlers.
- shows the message at the head of the queue
- hides and dismisses the message when it gets dismissed through the API

BUG=1123947
R=lazzzis@chromium.org

Change-Id: Ifa3fbba6162323bdc79305796472fdaea0829ae3
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2416740
Commit-Queue: Pavel Yatsuk <pavely@chromium.org>
Reviewed-by: default avatarMatthew Jones <mdjones@chromium.org>
Reviewed-by: default avatarLijin Shen <lazzzis@google.com>
Cr-Commit-Position: refs/heads/master@{#811037}
parent d398153f
...@@ -13,6 +13,7 @@ android_library("java") { ...@@ -13,6 +13,7 @@ android_library("java") {
deps = [ deps = [
"..:java", "..:java",
"//base:base_java", "//base:base_java",
"//third_party/android_deps:androidx_annotation_annotation_java",
"//ui/android:ui_full_java", "//ui/android:ui_full_java",
] ]
} }
...@@ -24,7 +25,10 @@ java_library("junit") { ...@@ -24,7 +25,10 @@ java_library("junit") {
sources = [ "java/src/org/chromium/components/messages/MessageQueueManagerImplTest.java" ] sources = [ "java/src/org/chromium/components/messages/MessageQueueManagerImplTest.java" ]
deps = [ deps = [
":java", ":java",
"..:java",
"//base:base_junit_test_support", "//base:base_junit_test_support",
"//third_party/android_deps:androidx_test_runner_java",
"//third_party/junit", "//third_party/junit",
"//third_party/mockito:mockito_java",
] ]
} }
...@@ -4,10 +4,17 @@ ...@@ -4,10 +4,17 @@
package org.chromium.components.messages; package org.chromium.components.messages;
import androidx.annotation.Nullable;
import org.chromium.base.UnownedUserData; import org.chromium.base.UnownedUserData;
import org.chromium.base.UnownedUserDataKey; import org.chromium.base.UnownedUserDataKey;
import org.chromium.ui.base.WindowAndroid; import org.chromium.ui.base.WindowAndroid;
import java.util.ArrayDeque;
import java.util.HashMap;
import java.util.Map;
import java.util.Queue;
/** /**
* A class managing the queue of messages. Its primary role is to decide when to show/hide current * A class managing the queue of messages. Its primary role is to decide when to show/hide current
* message and which message to show next. * message and which message to show next.
...@@ -16,6 +23,11 @@ class MessageQueueManagerImpl implements MessageQueueManager, UnownedUserData { ...@@ -16,6 +23,11 @@ class MessageQueueManagerImpl implements MessageQueueManager, UnownedUserData {
private static final UnownedUserDataKey<MessageQueueManagerImpl> KEY = private static final UnownedUserDataKey<MessageQueueManagerImpl> KEY =
new UnownedUserDataKey<>(MessageQueueManagerImpl.class); new UnownedUserDataKey<>(MessageQueueManagerImpl.class);
private final Queue<MessageStateHandler> mMessageQueue = new ArrayDeque<>();
private final Map<Object, MessageStateHandler> mMessageMap = new HashMap<>();
@Nullable
private MessageStateHandler mCurrentDisplayedMessage;
/** /**
* Get the activity's MessageQueueManager from the provided WindowAndroid. * Get the activity's MessageQueueManager from the provided WindowAndroid.
* @param window The window to get the manager from. * @param window The window to get the manager from.
...@@ -43,4 +55,44 @@ class MessageQueueManagerImpl implements MessageQueueManager, UnownedUserData { ...@@ -43,4 +55,44 @@ class MessageQueueManagerImpl implements MessageQueueManager, UnownedUserData {
public void destroy() { public void destroy() {
KEY.detachFromAllHosts(this); KEY.detachFromAllHosts(this);
} }
/**
* Enqueues a message. Associates the message with its key; the key is used later to dismiss the
* message. Displays the message if there is no other message shown.
* @param message The message to enqueue
* @param key The key to associate with this message.
*/
public void enqueueMessage(MessageStateHandler message, Object key) {
if (mMessageMap.containsKey(key)) {
throw new IllegalStateException("Message with the given key has already been enqueued");
}
mMessageMap.put(key, message);
mMessageQueue.add(message);
updateCurrentDisplayedMessage();
}
/**
* Dismisses a message specified by its key. Hdes the message if it is currently displayed.
* Displays the next message in the queue if available.
* @param key The key associated with the message to dismiss.
*/
public void dismissMessage(Object key) {
MessageStateHandler message = mMessageMap.get(key);
if (message == null) return;
mMessageMap.remove(key);
mMessageQueue.remove(message);
if (mCurrentDisplayedMessage == message) {
mCurrentDisplayedMessage.hide();
mCurrentDisplayedMessage = null;
}
message.dismiss();
updateCurrentDisplayedMessage();
}
private void updateCurrentDisplayedMessage() {
if (mCurrentDisplayedMessage != null) return;
if (mMessageQueue.isEmpty()) return;
mCurrentDisplayedMessage = mMessageQueue.element();
mCurrentDisplayedMessage.show();
}
} }
...@@ -4,8 +4,15 @@ ...@@ -4,8 +4,15 @@
package org.chromium.components.messages; package org.chromium.components.messages;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import androidx.test.filters.SmallTest;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.chromium.base.test.BaseRobolectricTestRunner; import org.chromium.base.test.BaseRobolectricTestRunner;
...@@ -14,8 +21,103 @@ import org.chromium.base.test.BaseRobolectricTestRunner; ...@@ -14,8 +21,103 @@ import org.chromium.base.test.BaseRobolectricTestRunner;
*/ */
@RunWith(BaseRobolectricTestRunner.class) @RunWith(BaseRobolectricTestRunner.class)
public class MessageQueueManagerImplTest { public class MessageQueueManagerImplTest {
/**
* Tests lifecycle of a single message:
* - enqueueMessage() calls show()
* - dismissMessage() calls hide() and dismiss()
*/
@Test
@SmallTest
public void testEnqueueMessage() {
MessageQueueManagerImpl queueManager = new MessageQueueManagerImpl();
MessageStateHandler m1 = Mockito.mock(MessageStateHandler.class);
MessageStateHandler m2 = Mockito.mock(MessageStateHandler.class);
queueManager.enqueueMessage(m1, m1);
verify(m1).show();
queueManager.dismissMessage(m1);
verify(m1).hide();
verify(m1).dismiss();
queueManager.enqueueMessage(m2, m2);
verify(m2).show();
queueManager.dismissMessage(m2);
verify(m2).hide();
verify(m2).dismiss();
}
/**
* Tests that, with multiple enqueued messages, only one message is shown at a time.
*/
@Test
@SmallTest
public void testOneMessageShownAtATime() {
MessageQueueManagerImpl queueManager = new MessageQueueManagerImpl();
MessageStateHandler m1 = Mockito.mock(MessageStateHandler.class);
MessageStateHandler m2 = Mockito.mock(MessageStateHandler.class);
queueManager.enqueueMessage(m1, m1);
queueManager.enqueueMessage(m2, m2);
verify(m1).show();
verify(m2, never()).show();
queueManager.dismissMessage(m1);
verify(m1).hide();
verify(m1).dismiss();
verify(m2).show();
}
/**
* Tests that, when the message is dismissed before it was shown, neither show() nor hide() is
* called.
*/
@Test
@SmallTest
public void testDismissBeforeShow() {
MessageQueueManagerImpl queueManager = new MessageQueueManagerImpl();
MessageStateHandler m1 = Mockito.mock(MessageStateHandler.class);
MessageStateHandler m2 = Mockito.mock(MessageStateHandler.class);
queueManager.enqueueMessage(m1, m1);
queueManager.enqueueMessage(m2, m2);
verify(m1).show();
verify(m2, never()).show();
queueManager.dismissMessage(m2);
verify(m2).dismiss();
queueManager.dismissMessage(m1);
verify(m2, never()).show();
verify(m2, never()).hide();
}
/**
* Tests that enqueueing two messages with the same key is not allowed, it results in
* IllegalStateException.
*/
@Test(expected = IllegalStateException.class)
@SmallTest
public void testEnqueueDuplicateKey() {
MessageQueueManagerImpl queueManager = new MessageQueueManagerImpl();
MessageStateHandler m1 = Mockito.mock(MessageStateHandler.class);
MessageStateHandler m2 = Mockito.mock(MessageStateHandler.class);
Object key = new Object();
queueManager.enqueueMessage(m1, key);
queueManager.enqueueMessage(m2, key);
}
/**
* Tests that dismissing a message more than once is handled correctly.
*/
@Test @Test
public void testCreateMessageQueueManager() { @SmallTest
public void testDismissMessageTwice() {
MessageQueueManagerImpl queueManager = new MessageQueueManagerImpl(); MessageQueueManagerImpl queueManager = new MessageQueueManagerImpl();
MessageStateHandler m1 = Mockito.mock(MessageStateHandler.class);
queueManager.enqueueMessage(m1, m1);
queueManager.dismissMessage(m1);
queueManager.dismissMessage(m1);
verify(m1, times(1)).dismiss();
} }
} }
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment