summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRitwika Mitra <ritwikam@google.com>2020-06-05 22:11:24 +0000
committerAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>2020-06-05 22:11:24 +0000
commite062fe978add037cc4c39c66011b156c8a525012 (patch)
tree79e7f917a010ca77797c81ca61f2916b33e88ae6
parentb7974d8cdb11ca230cd96b75238b8d2ef7d24527 (diff)
parentb666ba1f06ff76db79f6df1219cf08d58590f0fc (diff)
downloadCompanionDeviceSupport-e062fe978add037cc4c39c66011b156c8a525012.tar.gz
Test NotificationMsgDelegate and fix small bugs uncovered in tests. am: b666ba1f06
Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/apps/Car/CompanionDeviceSupport/+/11709211 Change-Id: I286a427a5b8706eca9024c8bb45c997054574664
-rw-r--r--src/com/android/car/companiondevicesupport/feature/notificationmsg/NotificationMsgDelegate.java29
-rw-r--r--tests/unit/src/com/android/car/companiondevicesupport/feature/notificationmsg/NotificationMsgDelegateTest.java349
2 files changed, 369 insertions, 9 deletions
diff --git a/src/com/android/car/companiondevicesupport/feature/notificationmsg/NotificationMsgDelegate.java b/src/com/android/car/companiondevicesupport/feature/notificationmsg/NotificationMsgDelegate.java
index 1956a10..e2c3ab7 100644
--- a/src/com/android/car/companiondevicesupport/feature/notificationmsg/NotificationMsgDelegate.java
+++ b/src/com/android/car/companiondevicesupport/feature/notificationmsg/NotificationMsgDelegate.java
@@ -45,6 +45,7 @@ import com.android.car.messenger.common.Message;
import com.android.car.messenger.common.ProjectionStateListener;
import com.android.car.messenger.common.SenderKey;
import com.android.car.messenger.common.Utils;
+import com.android.internal.annotations.VisibleForTesting;
import java.util.HashMap;
import java.util.List;
@@ -82,7 +83,7 @@ public class NotificationMsgDelegate extends BaseNotificationDelegate {
protected final Map<SenderKey, Bitmap> mOneOnOneConversationAvatarMap = new HashMap<>();
/** Tracks whether a projection application is active in the foreground. **/
- private ProjectionStateListener mProjectionStateListener;
+ private final ProjectionStateListener mProjectionStateListener;
public NotificationMsgDelegate(Context context) {
super(context, /* useLetterTile */ false);
@@ -187,19 +188,23 @@ public class NotificationMsgDelegate extends BaseNotificationDelegate {
ConversationNotification notification, String notificationKey) {
String deviceAddress = device.getDeviceId();
ConversationKey convoKey = new ConversationKey(deviceAddress, notificationKey);
- if (mNotificationInfos.containsKey(convoKey)) {
- logw(TAG, "Conversation already exists! " + notificationKey);
- }
if (!Utils.isValidConversationNotification(notification, /* isShallowCheck= */ false)) {
logd(TAG, "Failed to initialize new Conversation, object missing required fields");
return;
}
- ConversationNotificationInfo convoInfo = ConversationNotificationInfo.
- createConversationNotificationInfo(device.getDeviceName(), device.getDeviceId(),
- notification, notificationKey);
- mNotificationInfos.put(convoKey, convoInfo);
+ ConversationNotificationInfo convoInfo;
+ if (mNotificationInfos.containsKey(convoKey)) {
+ logw(TAG, "Conversation already exists! " + notificationKey);
+ convoInfo = mNotificationInfos.get(convoKey);
+ } else {
+ convoInfo = ConversationNotificationInfo.
+ createConversationNotificationInfo(device.getDeviceName(), device.getDeviceId(),
+ notification, notificationKey);
+ mNotificationInfos.put(convoKey, convoInfo);
+ }
+
String appDisplayName = convoInfo.getAppDisplayName();
@@ -242,7 +247,8 @@ public class NotificationMsgDelegate extends BaseNotificationDelegate {
if (!notificationInfo.isGroupConvo()) {
return mOneOnOneConversationAvatarMap.get(
SenderKey.createSenderKey(convoKey, message.getSender()));
- } else if (message.getSender().getAvatar() != null) {
+ } else if (message.getSender().getAvatar() != null
+ || !message.getSender().getAvatar().isEmpty()) {
byte[] iconArray = message.getSender().getAvatar().toByteArray();
return BitmapFactory.decodeByteArray(iconArray, 0, iconArray.length);
}
@@ -356,4 +362,9 @@ public class NotificationMsgDelegate extends BaseNotificationDelegate {
return ++NEXT_NOTIFICATION_CHANNEL_ID;
}
}
+
+ @VisibleForTesting
+ void setNotificationManager(NotificationManager manager) {
+ mNotificationManager = manager;
+ }
}
diff --git a/tests/unit/src/com/android/car/companiondevicesupport/feature/notificationmsg/NotificationMsgDelegateTest.java b/tests/unit/src/com/android/car/companiondevicesupport/feature/notificationmsg/NotificationMsgDelegateTest.java
new file mode 100644
index 0000000..6adbf61
--- /dev/null
+++ b/tests/unit/src/com/android/car/companiondevicesupport/feature/notificationmsg/NotificationMsgDelegateTest.java
@@ -0,0 +1,349 @@
+/*
+ * Copyright (C) 2020 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.car.companiondevicesupport.feature.notificationmsg;
+
+
+import static androidx.core.app.NotificationCompat.CATEGORY_MESSAGE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.content.Context;
+import android.graphics.drawable.Icon;
+
+import androidx.core.app.NotificationCompat;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.car.companiondevicesupport.api.external.CompanionDevice;
+import com.android.car.messenger.NotificationMsgProto.NotificationMsg.ConversationNotification;
+import com.android.car.messenger.NotificationMsgProto.NotificationMsg.MessagingStyle;
+import com.android.car.messenger.NotificationMsgProto.NotificationMsg.MessagingStyleMessage;
+import com.android.car.messenger.NotificationMsgProto.NotificationMsg.Person;
+import com.android.car.messenger.NotificationMsgProto.NotificationMsg.PhoneToCarMessage;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+public class NotificationMsgDelegateTest {
+ private static final String NOTIFICATION_KEY_1 = "notification_key_1";
+ private static final String NOTIFICATION_KEY_2 = "notification_key_2";
+
+ private static final String COMPANION_DEVICE_ID = "sampleId";
+ private static final String COMPANION_DEVICE_NAME = "sampleName";
+
+ private static final String MESSAGING_APP_NAME = "Messaging App";
+ private static final String MESSAGING_PACKAGE_NAME = "com.android.messaging.app";
+ private static final String CONVERSATION_TITLE = "Conversation";
+ private static final String USER_DISPLAY_NAME = "User";
+ private static final String SENDER_1 = "Sender";
+
+ private static final MessagingStyleMessage MESSAGE_2 = MessagingStyleMessage.newBuilder()
+ .setTextMessage("Message 2")
+ .setSender(Person.newBuilder()
+ .setName(SENDER_1))
+ .setTimestamp((long) 1577909718950f)
+ .build();
+
+ private static final MessagingStyle VALID_STYLE = MessagingStyle.newBuilder()
+ .setConvoTitle(CONVERSATION_TITLE)
+ .setUserDisplayName(USER_DISPLAY_NAME)
+ .setIsGroupConvo(false)
+ .addMessagingStyleMsg(MessagingStyleMessage.newBuilder()
+ .setTextMessage("Message 1")
+ .setSender(Person.newBuilder()
+ .setName(SENDER_1))
+ .setTimestamp((long) 1577909718050f)
+ .build())
+ .build();
+
+ private static final ConversationNotification VALID_CONVERSATION =
+ ConversationNotification.newBuilder()
+ .setMessagingAppDisplayName(MESSAGING_APP_NAME)
+ .setMessagingAppPackageName(MESSAGING_PACKAGE_NAME)
+ .setTimeMs((long) 1577909716000f)
+ .setMessagingStyle(VALID_STYLE)
+ .build();
+
+ private static final PhoneToCarMessage VALID_CONVERSATION_MSG = PhoneToCarMessage.newBuilder()
+ .setNotificationKey(NOTIFICATION_KEY_1)
+ .setConversation(VALID_CONVERSATION)
+ .build();
+
+ @Mock
+ CompanionDevice mCompanionDevice;
+ @Mock
+ NotificationManager mMockNotificationManager;
+
+ ArgumentCaptor<Notification> mNotificationCaptor =
+ ArgumentCaptor.forClass(Notification.class);
+ ArgumentCaptor<Integer> mNotificationIdCaptor = ArgumentCaptor.forClass(Integer.class);
+
+ Context mContext = ApplicationProvider.getApplicationContext();
+ NotificationMsgDelegate mNotificationMsgDelegate;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ when(mCompanionDevice.getDeviceId()).thenReturn(COMPANION_DEVICE_ID);
+ when(mCompanionDevice.getDeviceName()).thenReturn(COMPANION_DEVICE_NAME);
+
+ mNotificationMsgDelegate = new NotificationMsgDelegate(mContext);
+ mNotificationMsgDelegate.setNotificationManager(mMockNotificationManager);
+ }
+
+ @Test
+ public void newConversationShouldPostNewNotification() {
+ // Test that a new conversation notification is posted with the correct fields.
+ mNotificationMsgDelegate.onMessageReceived(mCompanionDevice, VALID_CONVERSATION_MSG);
+
+ verify(mMockNotificationManager).notify(anyInt(), mNotificationCaptor.capture());
+
+ Notification postedNotification = mNotificationCaptor.getValue();
+ verifyNotification(VALID_CONVERSATION, postedNotification);
+ }
+
+ @Test
+ public void multipleNewConversationShouldPostMultipleNewNotifications() {
+ mNotificationMsgDelegate.onMessageReceived(mCompanionDevice, VALID_CONVERSATION_MSG);
+
+ verify(mMockNotificationManager).notify(mNotificationIdCaptor.capture(),
+ any(Notification.class));
+ int firstNotificationId = mNotificationIdCaptor.getValue();
+
+ mNotificationMsgDelegate.onMessageReceived(mCompanionDevice,
+ createSecondConversation());
+ verify(mMockNotificationManager, times(2)).notify(mNotificationIdCaptor.capture(),
+ any(Notification.class));
+
+ // Verify the notification id is different than the first.
+ assertThat((long) mNotificationIdCaptor.getValue()).isNotEqualTo(firstNotificationId);
+ }
+
+ @Test
+ public void invalidConversationShouldDoNothing() {
+ // Test that a conversation without all the required fields is dropped.
+ PhoneToCarMessage newConvo = PhoneToCarMessage.newBuilder()
+ .setNotificationKey(NOTIFICATION_KEY_1)
+ .setConversation(VALID_CONVERSATION.toBuilder().clearMessagingStyle())
+ .build();
+ mNotificationMsgDelegate.onMessageReceived(mCompanionDevice, newConvo);
+
+ verify(mMockNotificationManager, never()).notify(anyInt(), any(Notification.class));
+ }
+
+ @Test
+ public void newMessageShouldUpdateConversationNotification() {
+ // Check whether a new message updates the notification of the conversation it belongs to.
+ mNotificationMsgDelegate.onMessageReceived(mCompanionDevice, VALID_CONVERSATION_MSG);
+ verify(mMockNotificationManager).notify(mNotificationIdCaptor.capture(),
+ any(Notification.class));
+ int notificationId = mNotificationIdCaptor.getValue();
+ int messageCount = VALID_CONVERSATION_MSG.getConversation().getMessagingStyle()
+ .getMessagingStyleMsgCount();
+
+ PhoneToCarMessage updateConvo = PhoneToCarMessage.newBuilder()
+ .setNotificationKey(NOTIFICATION_KEY_1)
+ .setMessage(MESSAGE_2)
+ .build();
+ mNotificationMsgDelegate.onMessageReceived(mCompanionDevice, updateConvo);
+
+ // Verify same notification id is posted twice.
+ verify(mMockNotificationManager, times(2)).notify(eq(notificationId),
+ mNotificationCaptor.capture());
+
+ // Verify the notification contains one more message.
+ NotificationCompat.MessagingStyle messagingStyle = getMessagingStyle(
+ mNotificationCaptor.getValue());
+ assertThat(messagingStyle.getMessages().size()).isEqualTo(messageCount + 1);
+
+ // Verify notification's latest message matches the new message.
+ verifyMessage(MESSAGE_2, messagingStyle.getMessages().get(messageCount));
+ }
+
+ @Test
+ public void existingConversationShouldUpdateNotification() {
+ // Test that a conversation that already exists, but gets a new conversation message
+ // is updated with the new conversation metadata.
+ mNotificationMsgDelegate.onMessageReceived(mCompanionDevice, VALID_CONVERSATION_MSG);
+ verify(mMockNotificationManager).notify(mNotificationIdCaptor.capture(),
+ any(Notification.class));
+ int notificationId = mNotificationIdCaptor.getValue();
+
+ ConversationNotification updatedConversation = addSecondMessageToConversation().toBuilder()
+ .setMessagingAppDisplayName("New Messaging App")
+ .build();
+ mNotificationMsgDelegate.onMessageReceived(mCompanionDevice, PhoneToCarMessage.newBuilder()
+ .setNotificationKey(NOTIFICATION_KEY_1)
+ .setConversation(updatedConversation)
+ .build());
+
+ verify(mMockNotificationManager, times(2)).notify(eq(notificationId),
+ mNotificationCaptor.capture());
+ Notification postedNotification = mNotificationCaptor.getValue();
+
+ // Verify Conversation level metadata does NOT change
+ verifyConversationLevelMetadata(VALID_CONVERSATION, postedNotification);
+ // Verify the MessagingStyle metadata does update with the new message.
+ verifyMessagingStyle(updatedConversation.getMessagingStyle(), postedNotification);
+ }
+
+ @Test
+ public void messageForUnknownConversationShouldDoNothing() {
+ // A message for an unknown conversation should be dropped.
+ PhoneToCarMessage updateConvo = PhoneToCarMessage.newBuilder()
+ .setNotificationKey(NOTIFICATION_KEY_1)
+ .setMessage(MESSAGE_2)
+ .build();
+ mNotificationMsgDelegate.onMessageReceived(mCompanionDevice, updateConvo);
+
+ verify(mMockNotificationManager, never()).notify(anyInt(), any(Notification.class));
+ }
+
+ @Test
+ public void invalidMessageShouldDoNothing() {
+ // Message without all the required fields is dropped.
+ mNotificationMsgDelegate.onMessageReceived(mCompanionDevice, VALID_CONVERSATION_MSG);
+
+ // Create a MessagingStyleMessage without a required field (Sender information).
+ MessagingStyleMessage invalidMsgStyleMessage = MessagingStyleMessage.newBuilder()
+ .setTextMessage("Message 2")
+ .setTimestamp((long) 1577909718950f)
+ .build();
+ PhoneToCarMessage invalidMessage = PhoneToCarMessage.newBuilder()
+ .setNotificationKey(NOTIFICATION_KEY_1)
+ .setMessage(invalidMsgStyleMessage)
+ .build();
+ mNotificationMsgDelegate.onMessageReceived(mCompanionDevice, invalidMessage);
+
+ // Verify only one notification is posted, and never updated.
+ verify(mMockNotificationManager).notify(anyInt(), any(Notification.class));
+ }
+
+ private void verifyNotification(ConversationNotification expected, Notification notification) {
+ verifyConversationLevelMetadata(expected, notification);
+ verifyMessagingStyle(expected.getMessagingStyle(), notification);
+ }
+
+ /**
+ * Verifies the conversation level metadata and other aspects of a notification that do not
+ * change when a new message is added to it (such as the actions, intents).
+ */
+ private void verifyConversationLevelMetadata(ConversationNotification expected,
+ Notification notification) {
+ assertThat(notification.category).isEqualTo(CATEGORY_MESSAGE);
+
+ assertThat(notification.getSmallIcon()).isNotNull();
+ if (!expected.getAppIcon().isEmpty()) {
+ byte[] iconBytes = expected.getAppIcon().toByteArray();
+ Icon appIcon = Icon.createWithData(iconBytes, 0, iconBytes.length);
+ assertThat(notification.getSmallIcon()).isEqualTo(appIcon);
+ }
+
+ assertThat(notification.deleteIntent).isNotNull();
+
+ if (expected.getMessagingAppPackageName() != null) {
+ CharSequence appName = notification.extras.getCharSequence(
+ Notification.EXTRA_SUBSTITUTE_APP_NAME);
+ assertThat(appName).isEqualTo(expected.getMessagingAppDisplayName());
+ }
+
+ assertThat(notification.actions.length).isEqualTo(2);
+ for (NotificationCompat.Action action : getAllActions(notification)) {
+ if (action.getSemanticAction() == NotificationCompat.Action.SEMANTIC_ACTION_REPLY) {
+ assertThat(action.getRemoteInputs().length).isEqualTo(1);
+ }
+ assertThat(action.getShowsUserInterface()).isFalse();
+ }
+ }
+
+ private void verifyMessagingStyle(MessagingStyle expected, Notification notification) {
+ final NotificationCompat.MessagingStyle messagingStyle = getMessagingStyle(notification);
+
+ assertThat(messagingStyle.getUser().getName()).isEqualTo(expected.getUserDisplayName());
+ assertThat(messagingStyle.isGroupConversation()).isEqualTo(expected.getIsGroupConvo());
+ assertThat(messagingStyle.getMessages().size()).isEqualTo(expected.getMessagingStyleMsgCount());
+
+ for (int i = 0; i < expected.getMessagingStyleMsgCount(); i++) {
+ MessagingStyleMessage expectedMsg = expected.getMessagingStyleMsg(i);
+ NotificationCompat.MessagingStyle.Message actualMsg = messagingStyle.getMessages().get(
+ i);
+ verifyMessage(expectedMsg, actualMsg);
+
+ }
+ }
+
+ private void verifyMessage(MessagingStyleMessage expectedMsg,
+ NotificationCompat.MessagingStyle.Message actualMsg) {
+ assertThat(actualMsg.getTimestamp()).isEqualTo(expectedMsg.getTimestamp());
+ assertThat(actualMsg.getText()).isEqualTo(expectedMsg.getTextMessage());
+
+ Person expectedSender = expectedMsg.getSender();
+ androidx.core.app.Person actualSender = actualMsg.getPerson();
+ assertThat(actualSender.getName()).isEqualTo(expectedSender.getName());
+ if (!expectedSender.getAvatar().isEmpty()) {
+ assertThat(actualSender.getIcon()).isNotNull();
+ } else {
+ assertThat(actualSender.getIcon()).isNull();
+ }
+ }
+
+ private NotificationCompat.MessagingStyle getMessagingStyle(Notification notification) {
+ return NotificationCompat.MessagingStyle.extractMessagingStyleFromNotification(
+ notification);
+ }
+
+ private List<NotificationCompat.Action> getAllActions(Notification notification) {
+ List<NotificationCompat.Action> actions = new ArrayList<>();
+ actions.addAll(NotificationCompat.getInvisibleActions(notification));
+ for (int i = 0; i < NotificationCompat.getActionCount(notification); i++) {
+ actions.add(NotificationCompat.getAction(notification, i));
+ }
+ return actions;
+ }
+
+ private PhoneToCarMessage createSecondConversation() {
+ return VALID_CONVERSATION_MSG.toBuilder()
+ .setNotificationKey(NOTIFICATION_KEY_2)
+ .setConversation(addSecondMessageToConversation())
+ .build();
+ }
+
+ private ConversationNotification addSecondMessageToConversation() {
+ return VALID_CONVERSATION.toBuilder()
+ .setMessagingStyle(
+ VALID_STYLE.toBuilder().addMessagingStyleMsg(MESSAGE_2)).build();
+ }
+}