Implement instant share browser messages.

Some parts of actions and data in the strings are not finished as it
relies on more information to be plumbed into the message first.

The tests re-use shared test helper logic, which needed to be moved to
be accessible from share code.

Bug: 369163940
Change-Id: I732d2b10195d76a6c49b68794c9f562c1ecc5c82
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5906783
Commit-Queue: Shakti Sahu <[email protected]>
Reviewed-by: Calder Kitagawa <[email protected]>
Auto-Submit: Sky Malice <[email protected]>
Reviewed-by: Shakti Sahu <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1364269}
diff --git a/chrome/browser/data_sharing/BUILD.gn b/chrome/browser/data_sharing/BUILD.gn
index 0093dc9..f095ab8 100644
--- a/chrome/browser/data_sharing/BUILD.gn
+++ b/chrome/browser/data_sharing/BUILD.gn
@@ -20,6 +20,7 @@
 
     deps = [
       ":java",
+      ":messages_java",
       "//base:base_java",
       "//base:service_loader_java",
       "//build/android:build_java",
@@ -84,7 +85,6 @@
     sources = [
       "android/java/src/org/chromium/chrome/browser/data_sharing/DataSharingImplFactory.java",
       "android/java/src/org/chromium/chrome/browser/data_sharing/DataSharingNotificationManager.java",
-      "android/java/src/org/chromium/chrome/browser/data_sharing/InstantMessageDelegateImpl.java",
       "android/java/src/org/chromium/chrome/browser/data_sharing/MemberPickerListenerImpl.java",
       "android/java/src/org/chromium/chrome/browser/data_sharing/NoOpDataSharingSDKDelegateImpl.java",
     ]
@@ -96,21 +96,15 @@
       "//chrome/browser/notifications:java",
       "//chrome/browser/profiles/android:java",
       "//chrome/browser/tab:java",
-      "//chrome/browser/tab_group:java",
-      "//chrome/browser/tab_group_sync/messaging/android:factory_java",
       "//chrome/browser/ui/android/strings:ui_strings_grd",
       "//components/browser_ui/notifications/android:java",
       "//components/browser_ui/styles/android:java_resources",
       "//components/data_sharing/public:public_java",
       "//components/data_sharing/public/protocol:proto_java",
-      "//components/messages/android:java",
-      "//components/saved_tab_groups/messaging/android:java",
       "//third_party/android_deps:protobuf_lite_runtime_java",
       "//third_party/android_sdk:android_window_extensions_java",
       "//third_party/androidx:androidx_annotation_annotation_java",
-      "//third_party/androidx:androidx_core_core_java",
       "//third_party/jni_zero:jni_zero_java",
-      "//ui/android:ui_java",
       "//url:url_java",
     ]
   }
@@ -139,6 +133,29 @@
     ]
   }
 
+  android_library("messages_java") {
+    resources_package = "org.chromium.chrome.browser.data_sharing"
+    sources = [ "android/java/src/org/chromium/chrome/browser/data_sharing/InstantMessageDelegateImpl.java" ]
+
+    deps = [
+      ":java_resources",
+      "//base:base_java",
+      "//base:supplier_java",
+      "//chrome/android:chrome_app_java_resources",
+      "//chrome/browser/profiles/android:java",
+      "//chrome/browser/tab:java",
+      "//chrome/browser/tab_group:java",
+      "//chrome/browser/tab_group_sync/messaging/android:factory_java",
+      "//components/data_sharing/public:public_java",
+      "//components/messages/android:java",
+      "//components/saved_tab_groups/messaging/android:java",
+      "//components/saved_tab_groups/public:java",
+      "//third_party/androidx:androidx_annotation_annotation_java",
+      "//third_party/androidx:androidx_core_core_java",
+      "//ui/android:ui_java",
+    ]
+  }
+
   android_library("invitation_dialog_java") {
     resources_package =
         "org.chromium.chrome.browser.data_sharing.ui.invitation_dialog"
@@ -159,21 +176,30 @@
     sources = [
       "android/java/src/org/chromium/chrome/browser/data_sharing/DataSharingNotificationManagerUnitTest.java",
       "android/java/src/org/chromium/chrome/browser/data_sharing/DataSharingTabGroupUtilsUnitTest.java",
+      "android/java/src/org/chromium/chrome/browser/data_sharing/InstantMessageDelegateImplUnitTest.java",
       "android/java/src/org/chromium/chrome/browser/data_sharing/ui/shared_image_tiles/SharedImageTilesCoordinatorUnitTest.java",
     ]
     deps = [
       ":java",
+      ":messages_java",
       ":shared_image_tiles_java",
       "//base:base_java",
       "//base:base_junit_test_support",
       "//chrome/browser/notifications:java",
       "//chrome/browser/profiles/android:java",
       "//chrome/browser/tab:java",
+      "//chrome/browser/tab_group:java",
       "//chrome/browser/tab_group_sync:factory_java",
+      "//chrome/browser/tab_group_sync/messaging/android:factory_java",
       "//chrome/browser/tabmodel:java",
       "//chrome/test/android:chrome_java_unit_test_support",
       "//components/browser_ui/notifications/android:java",
+      "//components/data_sharing:test_support_java",
       "//components/data_sharing/public:public_java",
+      "//components/messages/android:factory_java",
+      "//components/messages/android:java",
+      "//components/messages/android:manager_java",
+      "//components/saved_tab_groups/messaging/android:java",
       "//components/saved_tab_groups/public:java",
       "//content/public/android:content_full_java",
       "//third_party/android_deps:robolectric_all_java",
diff --git a/chrome/browser/data_sharing/android/DEPS b/chrome/browser/data_sharing/android/DEPS
index c66e922..94b38565 100644
--- a/chrome/browser/data_sharing/android/DEPS
+++ b/chrome/browser/data_sharing/android/DEPS
@@ -1,3 +1,4 @@
 include_rules = [
   "+components/browser_ui/share",
+  "+components/saved_tab_groups/messaging",
 ]
diff --git a/chrome/browser/data_sharing/android/java/src/org/chromium/chrome/browser/data_sharing/InstantMessageDelegateImpl.java b/chrome/browser/data_sharing/android/java/src/org/chromium/chrome/browser/data_sharing/InstantMessageDelegateImpl.java
index fdbcbb0..f4d9744b 100644
--- a/chrome/browser/data_sharing/android/java/src/org/chromium/chrome/browser/data_sharing/InstantMessageDelegateImpl.java
+++ b/chrome/browser/data_sharing/android/java/src/org/chromium/chrome/browser/data_sharing/InstantMessageDelegateImpl.java
@@ -4,20 +4,33 @@
 
 package org.chromium.chrome.browser.data_sharing;
 
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.core.content.ContextCompat;
 import androidx.core.util.Pair;
 
+import org.chromium.base.Token;
+import org.chromium.base.supplier.Supplier;
 import org.chromium.chrome.browser.profiles.Profile;
+import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.tab_group_sync.messaging.MessagingBackendServiceFactory;
 import org.chromium.chrome.browser.tasks.tab_groups.TabGroupModelFilter;
+import org.chromium.components.messages.MessageBannerProperties;
 import org.chromium.components.messages.MessageDispatcher;
 import org.chromium.components.messages.MessageDispatcherProvider;
+import org.chromium.components.messages.MessageIdentifier;
+import org.chromium.components.messages.PrimaryActionClickBehavior;
 import org.chromium.components.tab_group_sync.messaging.InstantMessage;
 import org.chromium.components.tab_group_sync.messaging.InstantNotificationLevel;
-import org.chromium.components.tab_group_sync.messaging.InstantNotificationType;
+import org.chromium.components.tab_group_sync.messaging.MessageAttribution;
 import org.chromium.components.tab_group_sync.messaging.MessagingBackendService;
 import org.chromium.components.tab_group_sync.messaging.MessagingBackendService.InstantMessageDelegate;
 import org.chromium.components.tab_group_sync.messaging.UserAction;
 import org.chromium.ui.base.WindowAndroid;
+import org.chromium.ui.modelutil.PropertyModel;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -28,8 +41,7 @@
  * scoped by profile. This class should be attached/detached by all windows.
  */
 public class InstantMessageDelegateImpl implements InstantMessageDelegate {
-    private final List<Pair<MessageDispatcher, TabGroupModelFilter>> mAttachedWindows =
-            new ArrayList<>();
+    private final List<Pair<WindowAndroid, TabGroupModelFilter>> mAttachList = new ArrayList<>();
 
     /**
      * @param profile The current profile to get dependencies with.
@@ -45,42 +57,199 @@
      * @param windowAndroid The window that can be used for showing messages.
      * @param tabGroupModelFilter The tab model and group filter for the given window.
      */
-    public void attachWindow(WindowAndroid windowAndroid, TabGroupModelFilter tabGroupModelFilter) {
-        MessageDispatcher messageDispatcher = MessageDispatcherProvider.from(windowAndroid);
-        mAttachedWindows.add(new Pair<>(messageDispatcher, tabGroupModelFilter));
+    public void attachWindow(
+            @NonNull WindowAndroid windowAndroid,
+            @NonNull TabGroupModelFilter tabGroupModelFilter) {
+        assert windowAndroid != null;
+        assert tabGroupModelFilter != null;
+        assert !tabGroupModelFilter.isIncognito();
+        mAttachList.add(new Pair<>(windowAndroid, tabGroupModelFilter));
     }
 
     /**
      * @param windowAndroid The window that is no longer usable for showing messages.
      */
-    public void detachWindow(WindowAndroid windowAndroid) {
-        MessageDispatcher messageDispatcher = MessageDispatcherProvider.from(windowAndroid);
-        mAttachedWindows.removeIf(p -> Objects.equals(p.first, messageDispatcher));
+    public void detachWindow(@NonNull WindowAndroid windowAndroid) {
+        assert windowAndroid != null;
+        mAttachList.removeIf(wa -> Objects.equals(wa.first, windowAndroid));
     }
 
     @Override
     public void displayInstantaneousMessage(InstantMessage message) {
-        @InstantNotificationType int type = message.type;
-        if (type == InstantNotificationType.CONFLICT_TAB_REMOVED) {
-            // TODO(https://crbug.com/369164214): Implement (or move below).
-        }
         if (message.level == InstantNotificationLevel.SYSTEM) {
             // TODO(https://crbug.com/369164214): Implement.
         } else if (message.level == InstantNotificationLevel.BROWSER) {
-            if (mAttachedWindows.size() == 0) {
-                return;
-            }
+            @Nullable
+            Pair<WindowAndroid, TabGroupModelFilter> attach = getAttach(message.attribution);
+            if (attach == null) return;
+            @NonNull WindowAndroid windowAndroid = attach.first;
+            @NonNull TabGroupModelFilter tabGroupModelFilter = attach.second;
+            @Nullable
+            MessageDispatcher messageDispatcher = MessageDispatcherProvider.from(windowAndroid);
+            @Nullable Context context = windowAndroid.getContext().get();
+            if (messageDispatcher == null || context == null) return;
 
             @UserAction int userAction = message.action;
             if (userAction == UserAction.TAB_REMOVED) {
-                // TODO(https://crbug.com/369163940): Implement.
+                showTabRemoved(message, context, messageDispatcher, tabGroupModelFilter);
             } else if (userAction == UserAction.TAB_NAVIGATED) {
-                // TODO(https://crbug.com/369163940): Implement.
+                showTabChange(message, context, messageDispatcher, tabGroupModelFilter);
             } else if (userAction == UserAction.COLLABORATION_USER_JOINED) {
-                // TODO(https://crbug.com/369163940): Implement.
+                showCollaborationUserJoined(message, context, messageDispatcher);
             } else if (userAction == UserAction.COLLABORATION_REMOVED) {
-                // TODO(https://crbug.com/369163940): Implement.
+                showCollaborationRemoved(message, context, messageDispatcher);
             }
         }
     }
+
+    private Pair<WindowAndroid, TabGroupModelFilter> getAttach(
+            MessageAttribution messageAttribution) {
+        if (mAttachList.size() == 0) {
+            return null;
+        }
+
+        if (messageAttribution == null
+                || messageAttribution.localTabGroupId == null
+                || messageAttribution.localTabGroupId.tabGroupId == null) {
+            // Message doesn't link to a window, show it arbitrarily.
+            return mAttachList.get(0);
+        }
+
+        @NonNull Token tabGroupId = messageAttribution.localTabGroupId.tabGroupId;
+        for (Pair<WindowAndroid, TabGroupModelFilter> attach : mAttachList) {
+            TabGroupModelFilter tabGroupModelFilter = attach.second;
+            int rootId = tabGroupModelFilter.getRootIdFromStableId(tabGroupId);
+            if (rootId == Tab.INVALID_TAB_ID) continue;
+
+            // If we had a valid rootId, this is the right window.
+            return attach;
+        }
+
+        // Tab group was deleted or window not active.
+        return null;
+    }
+
+    private String givenNameFromMessage(InstantMessage message) {
+        return message.attribution.triggeringUser.givenName;
+    }
+
+    private String tabTitleFromMessage(InstantMessage message) {
+        // TODO(https://crbug.com/369163940): Once the message stores this, we can return it.
+        return "ph1";
+    }
+
+    private String tabGroupTitleFromMessage(InstantMessage message) {
+        // TODO(https://crbug.com/369163940): Once the message stores this, we can return it.
+        return "ph2";
+    }
+
+    private Drawable iconFromMessage(InstantMessage message, Context context) {
+        // TODO(https://crbug.com/369163940): Fetch this, potentially async.
+        return ContextCompat.getDrawable(context, R.drawable.ic_features_24dp);
+    }
+
+    private void showTabRemoved(
+            InstantMessage message,
+            Context context,
+            MessageDispatcher messageDispatcher,
+            TabGroupModelFilter tabGroupModelFilter) {
+        String givenName = givenNameFromMessage(message);
+        String tabTitle = tabTitleFromMessage(message);
+        String title =
+                context.getString(
+                        R.string.data_sharing_browser_message_removed_tab, givenName, tabTitle);
+        String buttonText = context.getString(R.string.data_sharing_browser_message_reopen);
+        Drawable icon = iconFromMessage(message, context);
+        // TODO(https://crbug.com/369163940): Once the message has the url, we can restore.
+        showGenericMessage(
+                messageDispatcher,
+                MessageIdentifier.TAB_REMOVED_THROUGH_COLLABORATION,
+                title,
+                buttonText,
+                icon,
+                () -> {});
+    }
+
+    private void showTabChange(
+            InstantMessage message,
+            Context context,
+            MessageDispatcher messageDispatcher,
+            TabGroupModelFilter tabGroupModelFilter) {
+        String givenName = givenNameFromMessage(message);
+        String tabTitle = tabTitleFromMessage(message);
+        String title =
+                context.getString(
+                        R.string.data_sharing_browser_message_changed_tab, givenName, tabTitle);
+        String buttonText = context.getString(R.string.data_sharing_browser_message_reopen);
+        Drawable icon = iconFromMessage(message, context);
+        // TODO(https://crbug.com/369163940): Once the message has the url, we can restore.
+        showGenericMessage(
+                messageDispatcher,
+                MessageIdentifier.TAB_NAVIGATED_THROUGH_COLLABORATION,
+                title,
+                buttonText,
+                icon,
+                () -> {});
+    }
+
+    private void showCollaborationUserJoined(
+            InstantMessage message, Context context, MessageDispatcher messageDispatcher) {
+        String givenName = givenNameFromMessage(message);
+        String tabGroupTitle = tabGroupTitleFromMessage(message);
+        String title =
+                context.getString(
+                        R.string.data_sharing_browser_message_joined_tab_group,
+                        givenName,
+                        tabGroupTitle);
+        String buttonText = context.getString(R.string.data_sharing_browser_message_manage);
+        Drawable icon = iconFromMessage(message, context);
+        // TODO(https://crbug.com/369163940): Action should open manage sheet.
+        showGenericMessage(
+                messageDispatcher,
+                MessageIdentifier.COLLABORATION_USER_JOINED,
+                title,
+                buttonText,
+                icon,
+                () -> {});
+    }
+
+    private void showCollaborationRemoved(
+            InstantMessage message, Context context, MessageDispatcher messageDispatcher) {
+        String tabGroupTitle = tabGroupTitleFromMessage(message);
+        String title =
+                context.getString(
+                        R.string.data_sharing_browser_message_not_available, tabGroupTitle);
+        String buttonText = context.getString(R.string.data_sharing_invitation_failure_button);
+        Drawable icon = ContextCompat.getDrawable(context, R.drawable.ic_features_24dp);
+        showGenericMessage(
+                messageDispatcher,
+                MessageIdentifier.COLLABORATION_REMOVED,
+                title,
+                buttonText,
+                icon,
+                () -> {});
+    }
+
+    private void showGenericMessage(
+            MessageDispatcher messageDispatcher,
+            @MessageIdentifier int messageIdentifier,
+            String title,
+            String buttonText,
+            Drawable icon,
+            Runnable action) {
+        Supplier<Integer> onPrimary =
+                () -> {
+                    action.run();
+                    return PrimaryActionClickBehavior.DISMISS_IMMEDIATELY;
+                };
+        PropertyModel propertyModel =
+                new PropertyModel.Builder(MessageBannerProperties.ALL_KEYS)
+                        .with(MessageBannerProperties.MESSAGE_IDENTIFIER, messageIdentifier)
+                        .with(MessageBannerProperties.TITLE, title)
+                        .with(MessageBannerProperties.PRIMARY_BUTTON_TEXT, buttonText)
+                        .with(MessageBannerProperties.ICON, icon)
+                        .with(MessageBannerProperties.ON_PRIMARY_ACTION, onPrimary)
+                        .build();
+        messageDispatcher.enqueueWindowScopedMessage(propertyModel, /* highPriority= */ false);
+    }
 }
diff --git a/chrome/browser/data_sharing/android/java/src/org/chromium/chrome/browser/data_sharing/InstantMessageDelegateImplUnitTest.java b/chrome/browser/data_sharing/android/java/src/org/chromium/chrome/browser/data_sharing/InstantMessageDelegateImplUnitTest.java
new file mode 100644
index 0000000..942f66e5
--- /dev/null
+++ b/chrome/browser/data_sharing/android/java/src/org/chromium/chrome/browser/data_sharing/InstantMessageDelegateImplUnitTest.java
@@ -0,0 +1,159 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.data_sharing;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import static org.chromium.components.messages.MessageBannerProperties.MESSAGE_IDENTIFIER;
+import static org.chromium.components.messages.MessageBannerProperties.TITLE;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import org.chromium.base.Token;
+import org.chromium.base.UnownedUserDataHost;
+import org.chromium.base.test.BaseRobolectricTestRunner;
+import org.chromium.chrome.browser.profiles.Profile;
+import org.chromium.chrome.browser.tab.Tab;
+import org.chromium.chrome.browser.tab_group_sync.messaging.MessagingBackendServiceFactory;
+import org.chromium.chrome.browser.tasks.tab_groups.TabGroupModelFilter;
+import org.chromium.components.data_sharing.SharedGroupTestHelper;
+import org.chromium.components.messages.ManagedMessageDispatcher;
+import org.chromium.components.messages.MessageIdentifier;
+import org.chromium.components.messages.MessagesFactory;
+import org.chromium.components.tab_group_sync.LocalTabGroupId;
+import org.chromium.components.tab_group_sync.messaging.InstantMessage;
+import org.chromium.components.tab_group_sync.messaging.InstantNotificationLevel;
+import org.chromium.components.tab_group_sync.messaging.MessageAttribution;
+import org.chromium.components.tab_group_sync.messaging.MessagingBackendService;
+import org.chromium.components.tab_group_sync.messaging.UserAction;
+import org.chromium.ui.base.WindowAndroid;
+import org.chromium.ui.modelutil.PropertyModel;
+
+import java.lang.ref.WeakReference;
+
+/** Unit tests for {@link InstantMessageDelegateImpl}. */
+@RunWith(BaseRobolectricTestRunner.class)
+public class InstantMessageDelegateImplUnitTest {
+    private static final Token TAB_GROUP_ID = new Token(1L, 2L);
+    private static final int TAB_ID = 1;
+
+    @Rule public MockitoRule mMockitoRule = MockitoJUnit.rule();
+
+    @Mock private Profile mProfile;
+    @Mock private MessagingBackendService mMessagingBackendService;
+    @Mock private ManagedMessageDispatcher mManagedMessageDispatcher;
+    @Mock private WindowAndroid mWindowAndroid;
+    @Mock private TabGroupModelFilter mTabGroupModelFilter;
+
+    @Captor private ArgumentCaptor<PropertyModel> mPropertyModelCaptor;
+
+    private InstantMessageDelegateImpl mDelegate;
+
+    @Before
+    public void setUp() {
+        MessagingBackendServiceFactory.setForTesting(mMessagingBackendService);
+        when(mWindowAndroid.getUnownedUserDataHost()).thenReturn(new UnownedUserDataHost());
+        MessagesFactory.attachMessageDispatcher(mWindowAndroid, mManagedMessageDispatcher);
+        when(mWindowAndroid.getContext())
+                .thenReturn(new WeakReference<>(ApplicationProvider.getApplicationContext()));
+        when(mTabGroupModelFilter.getRootIdFromStableId(TAB_GROUP_ID)).thenReturn(TAB_ID);
+
+        mDelegate = new InstantMessageDelegateImpl(mProfile);
+        mDelegate.attachWindow(mWindowAndroid, mTabGroupModelFilter);
+    }
+
+    private InstantMessage newInstantMessage(@UserAction int action) {
+        MessageAttribution attribution = new MessageAttribution();
+        attribution.localTabGroupId = new LocalTabGroupId(TAB_GROUP_ID);
+        attribution.triggeringUser = SharedGroupTestHelper.GROUP_MEMBER1;
+        InstantMessage instantMessage = new InstantMessage();
+        instantMessage.attribution = attribution;
+        instantMessage.action = action;
+        instantMessage.level = InstantNotificationLevel.BROWSER;
+        return instantMessage;
+    }
+
+    @Test
+    public void testDisplayInstantaneousMessage_NotAttached() {
+        mDelegate.detachWindow(mWindowAndroid);
+        mDelegate.displayInstantaneousMessage(newInstantMessage(UserAction.TAB_REMOVED));
+        verify(mManagedMessageDispatcher, never()).enqueueWindowScopedMessage(any(), anyBoolean());
+    }
+
+    @Test
+    public void testDisplayInstantaneousMessage_NotInTabModel() {
+        when(mTabGroupModelFilter.getRootIdFromStableId(TAB_GROUP_ID))
+                .thenReturn(Tab.INVALID_TAB_ID);
+        mDelegate.displayInstantaneousMessage(newInstantMessage(UserAction.TAB_REMOVED));
+        verify(mManagedMessageDispatcher, never()).enqueueWindowScopedMessage(any(), anyBoolean());
+    }
+
+    @Test
+    public void testTabRemoved() {
+        mDelegate.displayInstantaneousMessage(newInstantMessage(UserAction.TAB_REMOVED));
+
+        verify(mManagedMessageDispatcher)
+                .enqueueWindowScopedMessage(mPropertyModelCaptor.capture(), anyBoolean());
+        PropertyModel propertyModel = mPropertyModelCaptor.getValue();
+        @MessageIdentifier int messageIdentifier = propertyModel.get(MESSAGE_IDENTIFIER);
+        assertEquals(MessageIdentifier.TAB_REMOVED_THROUGH_COLLABORATION, messageIdentifier);
+        String title = propertyModel.get(TITLE);
+        assertTrue(title.contains(SharedGroupTestHelper.GIVEN_NAME1));
+    }
+
+    @Test
+    public void testTabNavigated() {
+        mDelegate.displayInstantaneousMessage(newInstantMessage(UserAction.TAB_NAVIGATED));
+
+        verify(mManagedMessageDispatcher)
+                .enqueueWindowScopedMessage(mPropertyModelCaptor.capture(), anyBoolean());
+        PropertyModel propertyModel = mPropertyModelCaptor.getValue();
+        @MessageIdentifier int messageIdentifier = propertyModel.get(MESSAGE_IDENTIFIER);
+        assertEquals(MessageIdentifier.TAB_NAVIGATED_THROUGH_COLLABORATION, messageIdentifier);
+        String title = propertyModel.get(TITLE);
+        assertTrue(title.contains(SharedGroupTestHelper.GIVEN_NAME1));
+    }
+
+    @Test
+    public void testCollaborationUserJoined() {
+        mDelegate.displayInstantaneousMessage(
+                newInstantMessage(UserAction.COLLABORATION_USER_JOINED));
+
+        verify(mManagedMessageDispatcher)
+                .enqueueWindowScopedMessage(mPropertyModelCaptor.capture(), anyBoolean());
+        PropertyModel propertyModel = mPropertyModelCaptor.getValue();
+        @MessageIdentifier int messageIdentifier = propertyModel.get(MESSAGE_IDENTIFIER);
+        assertEquals(MessageIdentifier.COLLABORATION_USER_JOINED, messageIdentifier);
+        String title = propertyModel.get(TITLE);
+        assertTrue(title.contains(SharedGroupTestHelper.GIVEN_NAME1));
+    }
+
+    @Test
+    public void testCollaborationRemoved() {
+        mDelegate.displayInstantaneousMessage(newInstantMessage(UserAction.COLLABORATION_REMOVED));
+
+        verify(mManagedMessageDispatcher)
+                .enqueueWindowScopedMessage(mPropertyModelCaptor.capture(), anyBoolean());
+        PropertyModel propertyModel = mPropertyModelCaptor.getValue();
+        @MessageIdentifier int messageIdentifier = propertyModel.get(MESSAGE_IDENTIFIER);
+        assertEquals(MessageIdentifier.COLLABORATION_REMOVED, messageIdentifier);
+    }
+}