0

Fetch user avatars for collaboration messages.

Bug: 374806190
Change-Id: Ib45785ca7208a7b295de0f953d644faabb674ee5
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6020330
Code-Coverage: findit-for-me@appspot.gserviceaccount.com <findit-for-me@appspot.gserviceaccount.com>
Reviewed-by: Siddhartha S <ssid@chromium.org>
Reviewed-by: Calder Kitagawa <ckitagawa@chromium.org>
Commit-Queue: Siddhartha S <ssid@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1383287}
This commit is contained in:
Sky Malice
2024-11-14 22:44:20 +00:00
committed by Chromium LUCI CQ
parent 4e72956d07
commit f6b02b90d9
6 changed files with 123 additions and 34 deletions
chrome/browser/data_sharing/android/java/src/org/chromium/chrome/browser/data_sharing
components
collaboration
public
BUILD.gn
android
java
src
org
chromium
components
data_sharing
public
android
java
src
org
chromium
components

@@ -6,6 +6,8 @@ package org.chromium.chrome.browser.data_sharing;
import android.app.Activity; import android.app.Activity;
import android.content.Context; import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.text.TextUtils; import android.text.TextUtils;
@@ -14,6 +16,7 @@ import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat; import androidx.core.content.ContextCompat;
import org.chromium.base.Callback; import org.chromium.base.Callback;
import org.chromium.base.CallbackUtils;
import org.chromium.base.Token; import org.chromium.base.Token;
import org.chromium.base.supplier.Supplier; import org.chromium.base.supplier.Supplier;
import org.chromium.chrome.browser.collaboration.messaging.MessagingBackendServiceFactory; import org.chromium.chrome.browser.collaboration.messaging.MessagingBackendServiceFactory;
@@ -27,6 +30,10 @@ import org.chromium.components.collaboration.messaging.InstantNotificationLevel;
import org.chromium.components.collaboration.messaging.MessageUtils; import org.chromium.components.collaboration.messaging.MessageUtils;
import org.chromium.components.collaboration.messaging.MessagingBackendService; import org.chromium.components.collaboration.messaging.MessagingBackendService;
import org.chromium.components.collaboration.messaging.MessagingBackendService.InstantMessageDelegate; import org.chromium.components.collaboration.messaging.MessagingBackendService.InstantMessageDelegate;
import org.chromium.components.data_sharing.DataSharingUIDelegate;
import org.chromium.components.data_sharing.GroupMember;
import org.chromium.components.data_sharing.configs.DataSharingAvatarBitmapConfig;
import org.chromium.components.data_sharing.configs.DataSharingAvatarBitmapConfig.DataSharingAvatarCallback;
import org.chromium.components.messages.MessageBannerProperties; import org.chromium.components.messages.MessageBannerProperties;
import org.chromium.components.messages.MessageDispatcher; import org.chromium.components.messages.MessageDispatcher;
import org.chromium.components.messages.MessageDispatcherProvider; import org.chromium.components.messages.MessageDispatcherProvider;
@@ -34,6 +41,7 @@ import org.chromium.components.messages.MessageIdentifier;
import org.chromium.components.messages.PrimaryActionClickBehavior; import org.chromium.components.messages.PrimaryActionClickBehavior;
import org.chromium.ui.base.WindowAndroid; import org.chromium.ui.base.WindowAndroid;
import org.chromium.ui.modelutil.PropertyModel; import org.chromium.ui.modelutil.PropertyModel;
import org.chromium.ui.util.ColorUtils;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@@ -63,6 +71,7 @@ public class InstantMessageDelegateImpl implements InstantMessageDelegate {
} }
private final List<AttachedWindowInfo> mAttachList = new ArrayList<>(); private final List<AttachedWindowInfo> mAttachList = new ArrayList<>();
private final DataSharingUIDelegate mDataSharingUiDelegate;
/** /**
* @param profile The current profile to get dependencies with. * @param profile The current profile to get dependencies with.
@@ -72,6 +81,7 @@ public class InstantMessageDelegateImpl implements InstantMessageDelegate {
MessagingBackendService messagingBackendService = MessagingBackendService messagingBackendService =
MessagingBackendServiceFactory.getForProfile(profile); MessagingBackendServiceFactory.getForProfile(profile);
messagingBackendService.setInstantMessageDelegate(this); messagingBackendService.setInstantMessageDelegate(this);
mDataSharingUiDelegate = DataSharingServiceFactory.getForProfile(profile).getUiDelegate();
} }
/** /**
@@ -191,9 +201,21 @@ public class InstantMessageDelegateImpl implements InstantMessageDelegate {
return null; return null;
} }
private Drawable iconFromMessage(Context context) { private void fetchAvatarIconFromMessage(
// TODO(https://crbug.com/369163940): Fetch this, potentially async. Context context, GroupMember groupMember, Callback<Drawable> onDrawable) {
return ContextCompat.getDrawable(context, R.drawable.ic_features_24dp); DataSharingAvatarCallback onBitmap =
(Bitmap bitmap) -> onDrawable.onResult(new BitmapDrawable(bitmap));
int sizeInPixels =
context.getResources().getDimensionPixelSize(R.dimen.message_description_icon_size);
DataSharingAvatarBitmapConfig config =
new DataSharingAvatarBitmapConfig.Builder()
.setContext(context)
.setGroupMember(groupMember)
.setIsDarkMode(ColorUtils.inNightMode(context))
.setAvatarSizeInPixels(sizeInPixels)
.setDataSharingAvatarCallback(onBitmap)
.build();
mDataSharingUiDelegate.getAvatarBitmap(config);
} }
private void showTabRemoved( private void showTabRemoved(
@@ -207,16 +229,23 @@ public class InstantMessageDelegateImpl implements InstantMessageDelegate {
context.getString( context.getString(
R.string.data_sharing_browser_message_removed_tab, givenName, tabTitle); R.string.data_sharing_browser_message_removed_tab, givenName, tabTitle);
String buttonText = context.getString(R.string.data_sharing_browser_message_reopen); String buttonText = context.getString(R.string.data_sharing_browser_message_reopen);
Drawable icon = iconFromMessage(context); GroupMember groupMember = MessageUtils.extractMember(message);
// TODO(https://crbug.com/369163940): Once the message has the url, we can restore. // TODO(https://crbug.com/369163940): Once the message has the url, we can restore.
showGenericMessage( Runnable action = CallbackUtils.emptyRunnable();
messageDispatcher,
MessageIdentifier.TAB_REMOVED_THROUGH_COLLABORATION, fetchAvatarIconFromMessage(
title, context,
buttonText, groupMember,
icon, (icon) -> {
() -> {}, showGenericMessage(
onSuccess); messageDispatcher,
MessageIdentifier.TAB_REMOVED_THROUGH_COLLABORATION,
title,
buttonText,
icon,
action,
onSuccess);
});
} }
private void showTabChange( private void showTabChange(
@@ -230,16 +259,23 @@ public class InstantMessageDelegateImpl implements InstantMessageDelegate {
context.getString( context.getString(
R.string.data_sharing_browser_message_changed_tab, givenName, tabTitle); R.string.data_sharing_browser_message_changed_tab, givenName, tabTitle);
String buttonText = context.getString(R.string.data_sharing_browser_message_reopen); String buttonText = context.getString(R.string.data_sharing_browser_message_reopen);
Drawable icon = iconFromMessage(context); GroupMember groupMember = MessageUtils.extractMember(message);
// TODO(https://crbug.com/369163940): Once the message has the url, we can restore. // TODO(https://crbug.com/369163940): Once the message has the url, we can restore.
showGenericMessage( Runnable action = CallbackUtils.emptyRunnable();
messageDispatcher,
MessageIdentifier.TAB_NAVIGATED_THROUGH_COLLABORATION, fetchAvatarIconFromMessage(
title, context,
buttonText, groupMember,
icon, (icon) -> {
() -> {}, showGenericMessage(
onSuccess); messageDispatcher,
MessageIdentifier.TAB_NAVIGATED_THROUGH_COLLABORATION,
title,
buttonText,
icon,
action,
onSuccess);
});
} }
private void showCollaborationMemberAdded( private void showCollaborationMemberAdded(
@@ -258,7 +294,7 @@ public class InstantMessageDelegateImpl implements InstantMessageDelegate {
givenName, givenName,
tabGroupTitle); tabGroupTitle);
String buttonText = activity.getString(R.string.data_sharing_browser_message_manage); String buttonText = activity.getString(R.string.data_sharing_browser_message_manage);
Drawable icon = iconFromMessage(activity); GroupMember groupMember = MessageUtils.extractMember(message);
Runnable openManageSharingRunnable = Runnable openManageSharingRunnable =
() -> { () -> {
// TODO(crbug.com/379148260): Use shared #isCollaborationIdValid. // TODO(crbug.com/379148260): Use shared #isCollaborationIdValid.
@@ -266,14 +302,20 @@ public class InstantMessageDelegateImpl implements InstantMessageDelegate {
dataSharingTabManager.showManageSharing(activity, collaborationId); dataSharingTabManager.showManageSharing(activity, collaborationId);
} }
}; };
showGenericMessage(
messageDispatcher, fetchAvatarIconFromMessage(
MessageIdentifier.COLLABORATION_MEMBER_ADDED, activity,
title, groupMember,
buttonText, (icon) -> {
icon, showGenericMessage(
openManageSharingRunnable, messageDispatcher,
onSuccess); MessageIdentifier.COLLABORATION_MEMBER_ADDED,
title,
buttonText,
icon,
openManageSharingRunnable,
onSuccess);
});
} }
private void showCollaborationRemoved( private void showCollaborationRemoved(
@@ -294,7 +336,7 @@ public class InstantMessageDelegateImpl implements InstantMessageDelegate {
title, title,
buttonText, buttonText,
icon, icon,
() -> {}, CallbackUtils.emptyRunnable(),
onSuccess); onSuccess);
} }

@@ -25,6 +25,7 @@ import static org.chromium.components.messages.MessageBannerProperties.TITLE;
import static org.chromium.components.messages.PrimaryActionClickBehavior.DISMISS_IMMEDIATELY; import static org.chromium.components.messages.PrimaryActionClickBehavior.DISMISS_IMMEDIATELY;
import android.app.Activity; import android.app.Activity;
import android.graphics.Bitmap;
import androidx.test.ext.junit.rules.ActivityScenarioRule; import androidx.test.ext.junit.rules.ActivityScenarioRule;
@@ -54,6 +55,9 @@ import org.chromium.components.collaboration.messaging.MessageAttribution;
import org.chromium.components.collaboration.messaging.MessagingBackendService; import org.chromium.components.collaboration.messaging.MessagingBackendService;
import org.chromium.components.collaboration.messaging.TabGroupMessageMetadata; import org.chromium.components.collaboration.messaging.TabGroupMessageMetadata;
import org.chromium.components.collaboration.messaging.TabMessageMetadata; import org.chromium.components.collaboration.messaging.TabMessageMetadata;
import org.chromium.components.data_sharing.DataSharingService;
import org.chromium.components.data_sharing.DataSharingUIDelegate;
import org.chromium.components.data_sharing.configs.DataSharingAvatarBitmapConfig;
import org.chromium.components.messages.ManagedMessageDispatcher; import org.chromium.components.messages.ManagedMessageDispatcher;
import org.chromium.components.messages.MessageIdentifier; import org.chromium.components.messages.MessageIdentifier;
import org.chromium.components.messages.MessagesFactory; import org.chromium.components.messages.MessagesFactory;
@@ -81,12 +85,15 @@ public class InstantMessageDelegateImplUnitTest {
@Mock private Profile mProfile; @Mock private Profile mProfile;
@Mock private MessagingBackendService mMessagingBackendService; @Mock private MessagingBackendService mMessagingBackendService;
@Mock private DataSharingService mDataSharingService;
@Mock private DataSharingUIDelegate mDataSharingUiDelegate;
@Mock private ManagedMessageDispatcher mManagedMessageDispatcher; @Mock private ManagedMessageDispatcher mManagedMessageDispatcher;
@Mock private WindowAndroid mWindowAndroid; @Mock private WindowAndroid mWindowAndroid;
@Mock private TabGroupModelFilter mTabGroupModelFilter; @Mock private TabGroupModelFilter mTabGroupModelFilter;
@Mock private Callback<Boolean> mSuccessCallback; @Mock private Callback<Boolean> mSuccessCallback;
@Mock private DataSharingNotificationManager mDataSharingNotificationManager; @Mock private DataSharingNotificationManager mDataSharingNotificationManager;
@Mock private DataSharingTabManager mDataSharingTabManager; @Mock private DataSharingTabManager mDataSharingTabManager;
@Mock private Bitmap mAvatarBitmap;
@Captor private ArgumentCaptor<PropertyModel> mPropertyModelCaptor; @Captor private ArgumentCaptor<PropertyModel> mPropertyModelCaptor;
@@ -102,6 +109,13 @@ public class InstantMessageDelegateImplUnitTest {
private void onActivity(Activity activity) { private void onActivity(Activity activity) {
MessagingBackendServiceFactory.setForTesting(mMessagingBackendService); MessagingBackendServiceFactory.setForTesting(mMessagingBackendService);
DataSharingServiceFactory.setForTesting(mDataSharingService);
when(mDataSharingService.getUiDelegate()).thenReturn(mDataSharingUiDelegate);
MockitoHelper.doCallback(
(DataSharingAvatarBitmapConfig config) ->
config.getDataSharingAvatarCallback().onAvatarLoaded(mAvatarBitmap))
.when(mDataSharingUiDelegate)
.getAvatarBitmap(any());
when(mWindowAndroid.getUnownedUserDataHost()).thenReturn(mUnownedUserDataHost); when(mWindowAndroid.getUnownedUserDataHost()).thenReturn(mUnownedUserDataHost);
MessagesFactory.attachMessageDispatcher(mWindowAndroid, mManagedMessageDispatcher); MessagesFactory.attachMessageDispatcher(mWindowAndroid, mManagedMessageDispatcher);

@@ -143,6 +143,7 @@ if (is_android) {
":java", ":java",
"//base:base_java", "//base:base_java",
"//base:base_junit_test_support", "//base:base_junit_test_support",
"//components/data_sharing:test_support_java",
"//components/data_sharing/public:public_java", "//components/data_sharing/public:public_java",
"//components/saved_tab_groups/public:java", "//components/saved_tab_groups/public:java",
"//third_party/android_deps:robolectric_all_java", "//third_party/android_deps:robolectric_all_java",

@@ -7,6 +7,7 @@ package org.chromium.components.collaboration.messaging;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import org.chromium.base.Token; import org.chromium.base.Token;
import org.chromium.components.data_sharing.GroupMember;
/** Provides functions to safely read fields out of messages, performing null checks. */ /** Provides functions to safely read fields out of messages, performing null checks. */
public class MessageUtils { public class MessageUtils {
@@ -74,4 +75,15 @@ public class MessageUtils {
? null ? null
: message.attribution.collaborationId; : message.attribution.collaborationId;
} }
/** Returns a GroupMember associated with the message, prioritizing affected over triggering. */
public static GroupMember extractMember(@Nullable InstantMessage message) {
if (message == null || message.attribution == null) {
return null;
} else if (message.attribution.affectedUser != null) {
return message.attribution.affectedUser;
} else {
return message.attribution.triggeringUser;
}
}
} }

@@ -6,6 +6,9 @@ package org.chromium.components.collaboration.messaging;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.chromium.components.data_sharing.SharedGroupTestHelper.GROUP_MEMBER1;
import static org.chromium.components.data_sharing.SharedGroupTestHelper.GROUP_MEMBER2;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
@@ -125,4 +128,21 @@ public class MessageUtilsUnitTest {
assertEquals("", MessageUtils.extractTabGroupTitle(null)); assertEquals("", MessageUtils.extractTabGroupTitle(null));
} }
@Test
public void testExtractMember() {
assertEquals(null, MessageUtils.extractMember(null));
InstantMessage message = new InstantMessage();
assertEquals(null, MessageUtils.extractMember(message));
message.attribution = new MessageAttribution();
assertEquals(null, MessageUtils.extractMember(message));
message.attribution.triggeringUser = GROUP_MEMBER1;
assertEquals(GROUP_MEMBER1, MessageUtils.extractMember(message));
message.attribution.affectedUser = GROUP_MEMBER2;
assertEquals(GROUP_MEMBER2, MessageUtils.extractMember(message));
}
} }

@@ -19,15 +19,15 @@ public final class DataSharingAvatarBitmapConfig {
private final DataSharingAvatarCallback mDataSharingAvatarCallback; private final DataSharingAvatarCallback mDataSharingAvatarCallback;
/** Interface used to pass the result of avatar loading. */ /** Interface used to pass the result of avatar loading. */
@FunctionalInterface
public interface DataSharingAvatarCallback { public interface DataSharingAvatarCallback {
/** /**
* Called when the avatar bitmap is ready. * Called when the avatar bitmap is ready.
* *
* @param bitmap The loaded avatar bitmap. If might return null, if group member is * @param bitmap The loaded avatar bitmap. If might return null, if group member is invalid.
* invalid.
*/ */
default void onAvatarLoaded(Bitmap bitmap) {} void onAvatarLoaded(Bitmap bitmap);
} }
private DataSharingAvatarBitmapConfig(Builder builder) { private DataSharingAvatarBitmapConfig(Builder builder) {