diff options
| author | Ritwika Mitra <ritwikam@google.com> | 2020-06-12 21:35:35 +0000 |
|---|---|---|
| committer | Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com> | 2020-06-12 21:35:35 +0000 |
| commit | d70ab9c2df479de2c6a380ca54e26368f39bb27d (patch) | |
| tree | 93c5fccaa6e2e8e4775e2b82c24c8dfc42eba80e | |
| parent | 0f1e9d8b070dbe198a399d78017c1985c4256392 (diff) | |
| parent | f23e69ccab4ca4ea0238d3a36cd35829c8d30ae0 (diff) | |
| download | platform_packages_apps_Car_CompanionDeviceSupport-d70ab9c2df479de2c6a380ca54e26368f39bb27d.tar.gz platform_packages_apps_Car_CompanionDeviceSupport-d70ab9c2df479de2c6a380ca54e26368f39bb27d.tar.bz2 platform_packages_apps_Car_CompanionDeviceSupport-d70ab9c2df479de2c6a380ca54e26368f39bb27d.zip | |
Merge "Add more tests to NotificationMsgDelegate" into rvc-dev am: f23e69ccab
Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/apps/Car/CompanionDeviceSupport/+/11762783
Change-Id: I3f278355f935ebe3db4511110b6096f7cf535dad
2 files changed, 462 insertions, 21 deletions
diff --git a/src/com/android/car/companiondevicesupport/feature/notificationmsg/NotificationMsgDelegate.java b/src/com/android/car/companiondevicesupport/feature/notificationmsg/NotificationMsgDelegate.java index e2c3ab7..9c00b93 100644 --- a/src/com/android/car/companiondevicesupport/feature/notificationmsg/NotificationMsgDelegate.java +++ b/src/com/android/car/companiondevicesupport/feature/notificationmsg/NotificationMsgDelegate.java @@ -59,12 +59,12 @@ public class NotificationMsgDelegate extends BaseNotificationDelegate { private static final String TAG = "NotificationMsgDelegate"; /** Key for the Reply string in a {@link MapEntry}. **/ - private static final String REPLY_KEY = "REPLY"; + protected static final String REPLY_KEY = "REPLY"; /** * Value for {@link ClearAppDataRequest#getMessagingAppPackageName()}, representing * when all messaging applications' data should be removed. */ - private static final String REMOVE_ALL_APP_DATA = "ALL"; + protected static final String REMOVE_ALL_APP_DATA = "ALL"; private static final AudioAttributes AUDIO_ATTRIBUTES = new AudioAttributes.Builder() .setUsage(AudioAttributes.USAGE_NOTIFICATION) @@ -83,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 final ProjectionStateListener mProjectionStateListener; + private ProjectionStateListener mProjectionStateListener; public NotificationMsgDelegate(Context context) { super(context, /* useLetterTile */ false); @@ -279,9 +279,9 @@ public class NotificationMsgDelegate extends BaseNotificationDelegate { mAppNameToChannel.put(appDisplayName, new NotificationChannelWrapper(appDisplayName)); } - boolean isProjectionActive = mProjectionStateListener.isProjectionInActiveForeground( - mConnectedDeviceBluetoothAddress); - return mAppNameToChannel.get(appDisplayName).getChannelId(isProjectionActive); + return mAppNameToChannel.get(appDisplayName).getChannelId( + mProjectionStateListener.isProjectionInActiveForeground( + mConnectedDeviceBluetoothAddress)); } private void createNewMessage(String deviceAddress, MessagingStyleMessage messagingStyleMessage, @@ -367,4 +367,9 @@ public class NotificationMsgDelegate extends BaseNotificationDelegate { void setNotificationManager(NotificationManager manager) { mNotificationManager = manager; } + + @VisibleForTesting + void setProjectionStateListener(ProjectionStateListener listener) { + mProjectionStateListener = listener; + } } 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 index 6adbf61..e41df2c 100644 --- a/tests/unit/src/com/android/car/companiondevicesupport/feature/notificationmsg/NotificationMsgDelegateTest.java +++ b/tests/unit/src/com/android/car/companiondevicesupport/feature/notificationmsg/NotificationMsgDelegateTest.java @@ -17,21 +17,33 @@ package com.android.car.companiondevicesupport.feature.notificationmsg; +import static android.app.NotificationManager.IMPORTANCE_HIGH; +import static android.app.NotificationManager.IMPORTANCE_LOW; + import static androidx.core.app.NotificationCompat.CATEGORY_MESSAGE; +import static com.android.car.messenger.NotificationMsgProto.NotificationMsg.Action.ActionName.DISMISS; +import static com.android.car.messenger.NotificationMsgProto.NotificationMsg.Action.ActionName.MARK_AS_READ; +import static com.android.car.messenger.NotificationMsgProto.NotificationMsg.Action.ActionName.REPLY; + 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.atLeastOnce; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; import android.app.Notification; +import android.app.NotificationChannel; import android.app.NotificationManager; import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; import android.graphics.drawable.Icon; import androidx.core.app.NotificationCompat; @@ -39,21 +51,35 @@ 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; +import com.android.car.messenger.NotificationMsgProto.NotificationMsg.Action; +import com.android.car.messenger.NotificationMsgProto.NotificationMsg.AvatarIconSync; +import com.android.car.messenger.NotificationMsgProto.NotificationMsg.CarToPhoneMessage; +import com.android.car.messenger.NotificationMsgProto.NotificationMsg.ClearAppDataRequest; 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.PhoneMetadata; import com.android.car.messenger.NotificationMsgProto.NotificationMsg.PhoneToCarMessage; +import com.android.car.messenger.common.ConversationKey; +import com.android.car.messenger.common.ProjectionStateListener; +import com.android.car.messenger.common.SenderKey; +import com.android.car.protobuf.ByteString; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; +import org.mockito.Captor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.io.ByteArrayOutputStream; import java.util.ArrayList; import java.util.List; +import java.util.UUID; @RunWith(AndroidJUnit4.class) public class NotificationMsgDelegateTest { @@ -62,15 +88,23 @@ public class NotificationMsgDelegateTest { private static final String COMPANION_DEVICE_ID = "sampleId"; private static final String COMPANION_DEVICE_NAME = "sampleName"; + private static final String DEVICE_ADDRESS = UUID.randomUUID().toString(); + private static final String BT_DEVICE_ADDRESS = UUID.randomUUID().toString(); 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 String MESSAGE_TEXT_1 = "Message 1"; + private static final String MESSAGE_TEXT_2 = "Message 2"; + + /** ConversationKey for {@link NotificationMsgDelegateTest#VALID_CONVERSATION_MSG}. **/ + private static final ConversationKey CONVERSATION_KEY_1 + = new ConversationKey(COMPANION_DEVICE_ID, NOTIFICATION_KEY_1); private static final MessagingStyleMessage MESSAGE_2 = MessagingStyleMessage.newBuilder() - .setTextMessage("Message 2") + .setTextMessage(MESSAGE_TEXT_2) .setSender(Person.newBuilder() .setName(SENDER_1)) .setTimestamp((long) 1577909718950f) @@ -81,7 +115,7 @@ public class NotificationMsgDelegateTest { .setUserDisplayName(USER_DISPLAY_NAME) .setIsGroupConvo(false) .addMessagingStyleMsg(MessagingStyleMessage.newBuilder() - .setTextMessage("Message 1") + .setTextMessage(MESSAGE_TEXT_1) .setSender(Person.newBuilder() .setName(SENDER_1)) .setTimestamp((long) 1577909718050f) @@ -101,14 +135,20 @@ public class NotificationMsgDelegateTest { .setConversation(VALID_CONVERSATION) .build(); + private Bitmap mIconBitmap; + private byte[] mIconByteArray; + @Mock CompanionDevice mCompanionDevice; @Mock NotificationManager mMockNotificationManager; + @Mock + ProjectionStateListener mMockProjectionStateListener; - ArgumentCaptor<Notification> mNotificationCaptor = - ArgumentCaptor.forClass(Notification.class); - ArgumentCaptor<Integer> mNotificationIdCaptor = ArgumentCaptor.forClass(Integer.class); + @Captor + ArgumentCaptor<Notification> mNotificationCaptor; + @Captor + ArgumentCaptor<Integer> mNotificationIdCaptor; Context mContext = ApplicationProvider.getApplicationContext(); NotificationMsgDelegate mNotificationMsgDelegate; @@ -122,6 +162,20 @@ public class NotificationMsgDelegateTest { mNotificationMsgDelegate = new NotificationMsgDelegate(mContext); mNotificationMsgDelegate.setNotificationManager(mMockNotificationManager); + mNotificationMsgDelegate.setProjectionStateListener(mMockProjectionStateListener); + + mIconBitmap = Bitmap.createBitmap(200, 200, Bitmap.Config.ARGB_8888); + + ByteArrayOutputStream stream = new ByteArrayOutputStream(); + mIconBitmap.compress(Bitmap.CompressFormat.PNG, 100, stream); + mIconByteArray = stream.toByteArray(); + stream.reset(); + + } + + @After + public void tearDown() { + mIconBitmap.recycle(); } @Test @@ -174,11 +228,8 @@ public class NotificationMsgDelegateTest { int messageCount = VALID_CONVERSATION_MSG.getConversation().getMessagingStyle() .getMessagingStyleMsgCount(); - PhoneToCarMessage updateConvo = PhoneToCarMessage.newBuilder() - .setNotificationKey(NOTIFICATION_KEY_1) - .setMessage(MESSAGE_2) - .build(); - mNotificationMsgDelegate.onMessageReceived(mCompanionDevice, updateConvo); + // Post a new message in this conversation. + updateConversationWithMessage2(); // Verify same notification id is posted twice. verify(mMockNotificationManager, times(2)).notify(eq(notificationId), @@ -223,11 +274,7 @@ public class NotificationMsgDelegateTest { @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); + updateConversationWithMessage2(); verify(mMockNotificationManager, never()).notify(anyInt(), any(Notification.class)); } @@ -252,6 +299,309 @@ public class NotificationMsgDelegateTest { verify(mMockNotificationManager).notify(anyInt(), any(Notification.class)); } + @Test + public void invalidAvatarIconSyncShouldDoNothing() { + mNotificationMsgDelegate.onMessageReceived(mCompanionDevice, VALID_CONVERSATION_MSG); + + // Create AvatarIconSync message without required field (Icon), ensure it's treated as an + // invalid message. + PhoneToCarMessage invalidMessage = PhoneToCarMessage.newBuilder() + .setNotificationKey(NOTIFICATION_KEY_1) + .setAvatarIconSync(AvatarIconSync.newBuilder() + .setPerson(Person.newBuilder() + .setName(SENDER_1)) + .build()) + .build(); + mNotificationMsgDelegate.onMessageReceived(mCompanionDevice, invalidMessage); + assertThat(mNotificationMsgDelegate.mOneOnOneConversationAvatarMap.isEmpty()).isTrue(); + } + + @Test + public void avatarIconSyncForGroupConversationShouldDoNothing() { + // We only sync avatars for 1-1 conversations. + sendGroupConversationMessage(); + + sendValidAvatarIconSyncMessage(); + + assertThat(mNotificationMsgDelegate.mOneOnOneConversationAvatarMap.isEmpty()).isTrue(); + } + + @Test + public void avatarIconSyncForUnknownConversationShouldDoNothing() { + // Drop avatar if it's for a conversation that is unknown. + sendValidAvatarIconSyncMessage(); + + assertThat(mNotificationMsgDelegate.mOneOnOneConversationAvatarMap.isEmpty()).isTrue(); + } + + @Test + public void avatarIconSyncSetsAvatarInNotification() { + // Check that a conversation that didn't have an avatar, but gets this message posts + // a notification with the avatar. + mNotificationMsgDelegate.onMessageReceived(mCompanionDevice, VALID_CONVERSATION_MSG); + verify(mMockNotificationManager).notify(anyInt(), mNotificationCaptor.capture()); + Icon notSetIcon = mNotificationCaptor.getValue().getLargeIcon(); + + sendValidAvatarIconSyncMessage(); + + // Post an update so we update the notification and can see the new icon. + updateConversationWithMessage2(); + + verify(mMockNotificationManager, times(2)).notify(anyInt(), mNotificationCaptor.capture()); + Icon newIcon = mNotificationCaptor.getValue().getLargeIcon(); + assertThat(newIcon).isNotEqualTo(notSetIcon); + } + + + @Test + public void avatarIconSyncStoresBitmapCorrectly() { + // Post a conversation notification first, so we don't drop the avatar message. + mNotificationMsgDelegate.onMessageReceived(mCompanionDevice, VALID_CONVERSATION_MSG); + + sendValidAvatarIconSyncMessage(); + + AvatarIconSync iconSync = createValidAvatarIconSync(); + SenderKey senderKey = SenderKey.createSenderKey(CONVERSATION_KEY_1, iconSync.getPerson()); + byte[] iconArray = iconSync.getPerson().getAvatar().toByteArray(); + Bitmap bitmap = BitmapFactory.decodeByteArray(iconArray, /* offset= */ 0, iconArray.length); + + assertThat(mNotificationMsgDelegate.mOneOnOneConversationAvatarMap).hasSize(1); + Bitmap actualBitmap = mNotificationMsgDelegate.mOneOnOneConversationAvatarMap.get( + senderKey); + assertThat(actualBitmap).isNotNull(); + assertThat(actualBitmap.sameAs(bitmap)).isTrue(); + } + + @Test + public void phoneMetadataUsedToCheckProjectionStatus_projectionActive() { + // Assert projectionListener gets called with phone metadata address. + when(mMockProjectionStateListener.isProjectionInActiveForeground( + BT_DEVICE_ADDRESS)).thenReturn(true); + sendValidPhoneMetadataMessage(); + + // Send a new conversation to trigger Projection State check. + mNotificationMsgDelegate.onMessageReceived(mCompanionDevice, VALID_CONVERSATION_MSG); + + verify(mMockProjectionStateListener).isProjectionInActiveForeground(BT_DEVICE_ADDRESS); + verify(mMockNotificationManager).notify(anyInt(), mNotificationCaptor.capture()); + checkChannelImportanceLevel( + mNotificationCaptor.getValue().getChannelId(), /* isLowImportance= */ true); + } + + @Test + public void phoneMetadataUsedCorrectlyToCheckProjectionStatus_projectionInactive() { + // Assert projectionListener gets called with phone metadata address. + when(mMockProjectionStateListener.isProjectionInActiveForeground( + BT_DEVICE_ADDRESS)).thenReturn(false); + sendValidPhoneMetadataMessage(); + + // Send a new conversation to trigger Projection State check. + mNotificationMsgDelegate.onMessageReceived(mCompanionDevice, VALID_CONVERSATION_MSG); + + verify(mMockProjectionStateListener).isProjectionInActiveForeground(BT_DEVICE_ADDRESS); + verify(mMockNotificationManager).notify(anyInt(), mNotificationCaptor.capture()); + checkChannelImportanceLevel( + mNotificationCaptor.getValue().getChannelId(), /* isLowImportance= */ false); + } + + @Test + public void delegateChecksProjectionStatus_projectionActive() { + // Assert projectionListener gets called with phone metadata address. + when(mMockProjectionStateListener.isProjectionInActiveForeground(null)).thenReturn(true); + + // Send a new conversation to trigger Projection State check. + mNotificationMsgDelegate.onMessageReceived(mCompanionDevice, VALID_CONVERSATION_MSG); + + verify(mMockProjectionStateListener).isProjectionInActiveForeground(null); + verify(mMockNotificationManager).notify(anyInt(), mNotificationCaptor.capture()); + checkChannelImportanceLevel( + mNotificationCaptor.getValue().getChannelId(), /* isLowImportance= */ true); + } + + @Test + public void delegateChecksProjectionStatus_projectionInactive() { + // Assert projectionListener gets called with phone metadata address. + when(mMockProjectionStateListener.isProjectionInActiveForeground(null)).thenReturn(false); + + // Send a new conversation to trigger Projection State check. + mNotificationMsgDelegate.onMessageReceived(mCompanionDevice, VALID_CONVERSATION_MSG); + + verify(mMockProjectionStateListener).isProjectionInActiveForeground(null); + verify(mMockNotificationManager).notify(anyInt(), mNotificationCaptor.capture()); + checkChannelImportanceLevel( + mNotificationCaptor.getValue().getChannelId(), /* isLowImportance= */ false); + } + + @Test + public void clearAllAppDataShouldClearInternalDataAndNotifications() { + mNotificationMsgDelegate.onMessageReceived(mCompanionDevice, VALID_CONVERSATION_MSG); + verify(mMockNotificationManager).notify(mNotificationIdCaptor.capture(), + any(Notification.class)); + int notificationId = mNotificationIdCaptor.getValue(); + + sendClearAppDataRequest(NotificationMsgDelegate.REMOVE_ALL_APP_DATA); + + verify(mMockNotificationManager).cancel(eq(notificationId)); + } + + @Test + public void clearSpecificAppDataShouldDoNothing() { + mNotificationMsgDelegate.onMessageReceived(mCompanionDevice, VALID_CONVERSATION_MSG); + verify(mMockNotificationManager).notify(anyInt(), any(Notification.class)); + + sendClearAppDataRequest(MESSAGING_PACKAGE_NAME); + + verify(mMockNotificationManager, never()).cancel(anyInt()); + } + + @Test + public void conversationsFromSameApplicationPostedOnSameChannel() { + mNotificationMsgDelegate.onMessageReceived(mCompanionDevice, VALID_CONVERSATION_MSG); + + verify(mMockNotificationManager).notify(anyInt(), mNotificationCaptor.capture()); + String firstChannelId = mNotificationCaptor.getValue().getChannelId(); + + mNotificationMsgDelegate.onMessageReceived(mCompanionDevice, + VALID_CONVERSATION_MSG.toBuilder() + .setNotificationKey(NOTIFICATION_KEY_2) + .setConversation(VALID_CONVERSATION.toBuilder() + .setMessagingStyle( + VALID_STYLE.toBuilder().addMessagingStyleMsg(MESSAGE_2)) + .build()) + .build()); + verify(mMockNotificationManager, times(2)).notify(anyInt(), mNotificationCaptor.capture()); + + assertThat(mNotificationCaptor.getValue().getChannelId()).isEqualTo(firstChannelId); + } + + @Test + public void messageDataNotSetShouldDoNothing() { + // For a PhoneToCarMessage w/ no MessageData + mNotificationMsgDelegate.onMessageReceived(mCompanionDevice, PhoneToCarMessage.newBuilder() + .setNotificationKey(NOTIFICATION_KEY_1) + .build()); + + verifyZeroInteractions(mMockNotificationManager); + } + + @Test + public void dismissShouldCreateCarToPhoneMessage() { + mNotificationMsgDelegate.onMessageReceived(mCompanionDevice, VALID_CONVERSATION_MSG); + + CarToPhoneMessage dismissMessage = mNotificationMsgDelegate.dismiss(CONVERSATION_KEY_1); + + verifyCarToPhoneActionMessage(dismissMessage, NOTIFICATION_KEY_1, DISMISS); + } + + @Test + public void dismissShouldDismissNotification() { + mNotificationMsgDelegate.onMessageReceived(mCompanionDevice, VALID_CONVERSATION_MSG); + verify(mMockNotificationManager).notify(mNotificationIdCaptor.capture(), + any(Notification.class)); + int notificationId = mNotificationIdCaptor.getValue(); + + mNotificationMsgDelegate.dismiss(CONVERSATION_KEY_1); + + verify(mMockNotificationManager).cancel(eq(notificationId)); + } + + @Test + public void markAsReadShouldCreateCarToPhoneMessage() { + // Mark message as read, verify message sent to phone. + mNotificationMsgDelegate.onMessageReceived(mCompanionDevice, VALID_CONVERSATION_MSG); + + CarToPhoneMessage markAsRead = mNotificationMsgDelegate.markAsRead(CONVERSATION_KEY_1); + + verifyCarToPhoneActionMessage(markAsRead, NOTIFICATION_KEY_1, MARK_AS_READ); + } + + @Test + public void markAsReadShouldExcludeMessageFromNotification() { + // Mark message as read, verify when new message comes in, read + // messages are not in notification. + mNotificationMsgDelegate.onMessageReceived(mCompanionDevice, VALID_CONVERSATION_MSG); + verify(mMockNotificationManager).notify(anyInt(), any(Notification.class)); + + mNotificationMsgDelegate.markAsRead(CONVERSATION_KEY_1); + // Post an update to this conversation to ensure the now read message is not in + // notification. + updateConversationWithMessage2(); + verify(mMockNotificationManager, times(2)).notify(anyInt(), + mNotificationCaptor.capture()); + + // Verify the notification contains only the latest message. + NotificationCompat.MessagingStyle messagingStyle = + NotificationCompat.MessagingStyle.extractMessagingStyleFromNotification( + mNotificationCaptor.getValue()); + assertThat(messagingStyle.getMessages().size()).isEqualTo(1); + verifyMessage(MESSAGE_2, messagingStyle.getMessages().get(0)); + + } + + @Test + public void replyShouldCreateCarToPhoneMessage() { + mNotificationMsgDelegate.onMessageReceived(mCompanionDevice, VALID_CONVERSATION_MSG); + + CarToPhoneMessage reply = mNotificationMsgDelegate.reply(CONVERSATION_KEY_1, + MESSAGE_TEXT_2); + Action replyAction = reply.getActionRequest(); + NotificationMsg.MapEntry replyEntry = replyAction.getMapEntry(0); + + verifyCarToPhoneActionMessage(reply, NOTIFICATION_KEY_1, REPLY); + assertThat(replyAction.getMapEntryCount()).isEqualTo(1); + assertThat(replyEntry.getKey()).isEqualTo(NotificationMsgDelegate.REPLY_KEY); + assertThat(replyEntry.getValue()).isEqualTo(MESSAGE_TEXT_2); + } + + @Test + public void onDestroyShouldClearInternalDataAndNotifications() { + mNotificationMsgDelegate.onMessageReceived(mCompanionDevice, VALID_CONVERSATION_MSG); + verify(mMockNotificationManager).notify(mNotificationIdCaptor.capture(), + any(Notification.class)); + int notificationId = mNotificationIdCaptor.getValue(); + sendValidAvatarIconSyncMessage(); + + mNotificationMsgDelegate.onDestroy(); + + assertThat(mNotificationMsgDelegate.mOneOnOneConversationAvatarMap.isEmpty()).isTrue(); + verify(mMockNotificationManager).cancel(eq(notificationId)); + } + + @Test + public void deviceDisconnectedShouldClearDeviceNotificationsAndMetadata() { + // Test that after a device disconnects, all the avatars, notifications for the device + // is removed. + mNotificationMsgDelegate.onMessageReceived(mCompanionDevice, VALID_CONVERSATION_MSG); + verify(mMockNotificationManager).notify(mNotificationIdCaptor.capture(), + any(Notification.class)); + int notificationId = mNotificationIdCaptor.getValue(); + sendValidAvatarIconSyncMessage(); + + mNotificationMsgDelegate.onDeviceDisconnected(COMPANION_DEVICE_ID); + + assertThat(mNotificationMsgDelegate.mOneOnOneConversationAvatarMap.isEmpty()).isTrue(); + verify(mMockNotificationManager).cancel(eq(notificationId)); + } + + @Test + public void deviceDisconnectedShouldResetProjectionDeviceAddress() { + // Test that after a device disconnects, then reconnects, the projection device address + // is reset. + when(mMockProjectionStateListener.isProjectionInActiveForeground( + BT_DEVICE_ADDRESS)).thenReturn(true); + sendValidPhoneMetadataMessage(); + + mNotificationMsgDelegate.onDeviceDisconnected(COMPANION_DEVICE_ID); + + // Now post a new notification for this device and ensure it is not posted silently. + mNotificationMsgDelegate.onMessageReceived(mCompanionDevice, VALID_CONVERSATION_MSG); + + verify(mMockProjectionStateListener).isProjectionInActiveForeground(null); + verify(mMockNotificationManager).notify(anyInt(), mNotificationCaptor.capture()); + checkChannelImportanceLevel( + mNotificationCaptor.getValue().getChannelId(), /* isLowImportance= */ false); + } + private void verifyNotification(ConversationNotification expected, Notification notification) { verifyConversationLevelMetadata(expected, notification); verifyMessagingStyle(expected.getMessagingStyle(), notification); @@ -320,6 +670,31 @@ public class NotificationMsgDelegateTest { } } + private void verifyCarToPhoneActionMessage(CarToPhoneMessage message, String notificationKey, + Action.ActionName actionName) { + assertThat(message.getNotificationKey()).isEqualTo(notificationKey); + assertThat(message.getActionRequest()).isNotNull(); + assertThat(message.getActionRequest().getNotificationKey()).isEqualTo(notificationKey); + assertThat(message.getActionRequest().getActionName()).isEqualTo(actionName); + } + + private void checkChannelImportanceLevel(String channelId, boolean isLowImportance) { + ArgumentCaptor<NotificationChannel> channelCaptor = ArgumentCaptor.forClass( + NotificationChannel.class); + verify(mMockNotificationManager, atLeastOnce()).createNotificationChannel( + channelCaptor.capture()); + + int desiredImportance = isLowImportance ? IMPORTANCE_LOW : IMPORTANCE_HIGH; + List<String> desiredImportanceChannelIds = new ArrayList<>(); + // Each messaging app has 2 channels, one high and one low importance. + for (NotificationChannel notificationChannel : channelCaptor.getAllValues()) { + if (notificationChannel.getImportance() == desiredImportance) { + desiredImportanceChannelIds.add(notificationChannel.getId()); + } + } + assertThat(desiredImportanceChannelIds.contains(channelId)).isTrue(); + } + private NotificationCompat.MessagingStyle getMessagingStyle(Notification notification) { return NotificationCompat.MessagingStyle.extractMessagingStyleFromNotification( notification); @@ -346,4 +721,65 @@ public class NotificationMsgDelegateTest { .setMessagingStyle( VALID_STYLE.toBuilder().addMessagingStyleMsg(MESSAGE_2)).build(); } + + private AvatarIconSync createValidAvatarIconSync() { + return AvatarIconSync.newBuilder() + .setMessagingAppPackageName(MESSAGING_PACKAGE_NAME) + .setMessagingAppDisplayName(MESSAGING_APP_NAME) + .setPerson(Person.newBuilder() + .setName(SENDER_1) + .setAvatar(ByteString.copyFrom(mIconByteArray)) + .build()) + .build(); + } + + /** + * Small helper method that updates {@link NotificationMsgDelegateTest#VALID_CONVERSATION} with + * a new message. + */ + private void updateConversationWithMessage2() { + PhoneToCarMessage updateConvo = PhoneToCarMessage.newBuilder() + .setNotificationKey(NOTIFICATION_KEY_1) + .setMessage(MESSAGE_2) + .build(); + mNotificationMsgDelegate.onMessageReceived(mCompanionDevice, updateConvo); + } + + private void sendValidAvatarIconSyncMessage() { + PhoneToCarMessage validMessage = PhoneToCarMessage.newBuilder() + .setNotificationKey(NOTIFICATION_KEY_1) + .setAvatarIconSync(createValidAvatarIconSync()) + .build(); + mNotificationMsgDelegate.onMessageReceived(mCompanionDevice, validMessage); + } + + private void sendValidPhoneMetadataMessage() { + PhoneToCarMessage metadataMessage = PhoneToCarMessage.newBuilder() + .setPhoneMetadata(PhoneMetadata.newBuilder() + .setBluetoothDeviceAddress(BT_DEVICE_ADDRESS) + .build()) + .build(); + mNotificationMsgDelegate.onMessageReceived(mCompanionDevice, metadataMessage); + } + + private void sendGroupConversationMessage() { + mNotificationMsgDelegate.onMessageReceived(mCompanionDevice, + VALID_CONVERSATION_MSG.toBuilder() + .setConversation(VALID_CONVERSATION.toBuilder() + .setMessagingStyle(VALID_STYLE.toBuilder() + .setIsGroupConvo(true) + .build()) + .build()) + .build()); + } + + + private void sendClearAppDataRequest(String messagingAppPackageName) { + mNotificationMsgDelegate.onMessageReceived(mCompanionDevice, + PhoneToCarMessage.newBuilder() + .setClearAppDataRequest(ClearAppDataRequest.newBuilder() + .setMessagingAppPackageName(messagingAppPackageName) + .build()) + .build()); + } } |
