package com.fsck.k9.notification; import java.util.ArrayList; import android.app.Notification; import android.app.PendingIntent; import android.content.Context; import android.support.v4.app.NotificationCompat.Action; import android.support.v4.app.NotificationCompat.Builder; import android.support.v4.app.NotificationCompat.Extender; import android.support.v4.app.NotificationCompat.WearableExtender; import com.fsck.k9.Account; import com.fsck.k9.K9; import com.fsck.k9.K9.NotificationQuickDelete; import com.fsck.k9.K9RobolectricTestRunner; import com.fsck.k9.MockHelper; import com.fsck.k9.R; import com.fsck.k9.activity.MessageReference; import com.fsck.k9.controller.MessagingController; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentMatcher; import org.robolectric.RuntimeEnvironment; import static org.junit.Assert.assertEquals; import static org.mockito.Matchers.any; import static org.mockito.Matchers.argThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @RunWith(K9RobolectricTestRunner.class) public class WearNotificationsTest { private static final int ACCOUNT_NUMBER = 42; private static final String ACCOUNT_NAME = "accountName"; private Account account; private Builder builder; private NotificationActionCreator actionCreator; private TestWearNotifications wearNotifications; private Notification notification; @Before public void setUp() throws Exception { account = createAccount(); notification = createNotification(); builder = createNotificationBuilder(notification); actionCreator = createNotificationActionCreator(); NotificationController controller = createNotificationController(RuntimeEnvironment.application, builder); MessagingController messagingController = createMessagingController(); wearNotifications = new TestWearNotifications(controller, actionCreator, messagingController); } @Test public void testBuildStackedNotification() throws Exception { disableOptionalActions(); int notificationIndex = 0; int notificationId = NotificationIds.getNewMailStackedNotificationId(account, notificationIndex); MessageReference messageReference = createMessageReference(1); NotificationContent content = createNotificationContent(messageReference); NotificationHolder holder = createNotificationHolder(notificationId, content); PendingIntent replyPendingIntent = createFakePendingIntent(1); when(actionCreator.createReplyPendingIntent(messageReference, notificationId)).thenReturn(replyPendingIntent); PendingIntent markAsReadPendingIntent = createFakePendingIntent(2); when(actionCreator.createMarkMessageAsReadPendingIntent(messageReference, notificationId)) .thenReturn(markAsReadPendingIntent); Notification result = wearNotifications.buildStackedNotification(account, holder); assertEquals(notification, result); verifyExtendWasOnlyCalledOnce(); verifyAddAction(R.drawable.ic_action_single_message_options_dark, "Reply", replyPendingIntent); verifyAddAction(R.drawable.ic_action_mark_as_read_dark, "Mark Read", markAsReadPendingIntent); verifyNumberOfActions(2); } @Test public void testBuildStackedNotificationWithDeleteActionEnabled() throws Exception { enableDeleteAction(); int notificationIndex = 0; int notificationId = NotificationIds.getNewMailStackedNotificationId(account, notificationIndex); MessageReference messageReference = createMessageReference(1); NotificationContent content = createNotificationContent(messageReference); NotificationHolder holder = createNotificationHolder(notificationId, content); PendingIntent deletePendingIntent = createFakePendingIntent(1); when(actionCreator.createDeleteMessagePendingIntent(messageReference, notificationId)) .thenReturn(deletePendingIntent); Notification result = wearNotifications.buildStackedNotification(account, holder); assertEquals(notification, result); verifyExtendWasOnlyCalledOnce(); verifyAddAction(R.drawable.ic_action_delete_dark, "Delete", deletePendingIntent); } @Test public void testBuildStackedNotificationWithArchiveActionEnabled() throws Exception { enableArchiveAction(); int notificationIndex = 0; int notificationId = NotificationIds.getNewMailStackedNotificationId(account, notificationIndex); MessageReference messageReference = createMessageReference(1); NotificationContent content = createNotificationContent(messageReference); NotificationHolder holder = createNotificationHolder(notificationId, content); PendingIntent archivePendingIntent = createFakePendingIntent(1); when(actionCreator.createArchiveMessagePendingIntent(messageReference, notificationId)) .thenReturn(archivePendingIntent); Notification result = wearNotifications.buildStackedNotification(account, holder); assertEquals(notification, result); verifyExtendWasOnlyCalledOnce(); verifyAddAction(R.drawable.ic_action_archive_dark, "Archive", archivePendingIntent); } @Test public void testBuildStackedNotificationWithMarkAsSpamActionEnabled() throws Exception { enableSpamAction(); int notificationIndex = 0; int notificationId = NotificationIds.getNewMailStackedNotificationId(account, notificationIndex); MessageReference messageReference = createMessageReference(1); NotificationContent content = createNotificationContent(messageReference); NotificationHolder holder = createNotificationHolder(notificationId, content); PendingIntent markAsSpamPendingIntent = createFakePendingIntent(1); when(actionCreator.createMarkMessageAsSpamPendingIntent(messageReference, notificationId)) .thenReturn(markAsSpamPendingIntent); Notification result = wearNotifications.buildStackedNotification(account, holder); assertEquals(notification, result); verifyExtendWasOnlyCalledOnce(); verifyAddAction(R.drawable.ic_action_spam_dark, "Spam", markAsSpamPendingIntent); } @Test public void testAddSummaryActions() throws Exception { disableOptionalSummaryActions(); int notificationId = NotificationIds.getNewMailSummaryNotificationId(account); ArrayList<MessageReference> messageReferences = createMessageReferenceList(); NotificationData notificationData = createNotificationData(messageReferences); PendingIntent markAllAsReadPendingIntent = createFakePendingIntent(1); when(actionCreator.getMarkAllAsReadPendingIntent(account, messageReferences, notificationId)) .thenReturn(markAllAsReadPendingIntent); wearNotifications.addSummaryActions(builder, notificationData); verifyExtendWasOnlyCalledOnce(); verifyAddAction(R.drawable.ic_action_mark_as_read_dark, "Mark All Read", markAllAsReadPendingIntent); verifyNumberOfActions(1); } @Test public void testAddSummaryActionsWithDeleteAllActionEnabled() throws Exception { enableDeleteAction(); int notificationId = NotificationIds.getNewMailSummaryNotificationId(account); ArrayList<MessageReference> messageReferences = createMessageReferenceList(); NotificationData notificationData = createNotificationData(messageReferences); PendingIntent deletePendingIntent = createFakePendingIntent(1); when(actionCreator.getDeleteAllPendingIntent(account, messageReferences, notificationId)) .thenReturn(deletePendingIntent); wearNotifications.addSummaryActions(builder, notificationData); verifyExtendWasOnlyCalledOnce(); verifyAddAction(R.drawable.ic_action_delete_dark, "Delete All", deletePendingIntent); } @Test public void testAddSummaryActionsWithArchiveAllActionEnabled() throws Exception { enableArchiveAction(); int notificationId = NotificationIds.getNewMailSummaryNotificationId(account); ArrayList<MessageReference> messageReferences = createMessageReferenceList(); NotificationData notificationData = createNotificationData(messageReferences); PendingIntent archivePendingIntent = createFakePendingIntent(1); when(actionCreator.createArchiveAllPendingIntent(account, messageReferences, notificationId)) .thenReturn(archivePendingIntent); wearNotifications.addSummaryActions(builder, notificationData); verifyExtendWasOnlyCalledOnce(); verifyAddAction(R.drawable.ic_action_archive_dark, "Archive All", archivePendingIntent); } private void disableOptionalActions() { disableDeleteAction(); disableArchiveAction(); disableSpamAction(); } private void disableDeleteAction() { K9.setNotificationQuickDeleteBehaviour(NotificationQuickDelete.NEVER); } private void disableArchiveAction() { when(account.getArchiveFolderName()).thenReturn(K9.FOLDER_NONE); } private void disableSpamAction() { when(account.getSpamFolderName()).thenReturn(K9.FOLDER_NONE); } private void enableDeleteAction() { K9.setNotificationQuickDeleteBehaviour(NotificationQuickDelete.ALWAYS); K9.setConfirmDeleteFromNotification(false); } private void enableArchiveAction() { when(account.getArchiveFolderName()).thenReturn("Archive"); } private void enableSpamAction() { when(account.getSpamFolderName()).thenReturn("Spam"); } private void disableOptionalSummaryActions() { disableDeleteAction(); disableArchiveAction(); } private Builder createNotificationBuilder(Notification notification) { Builder builder = MockHelper.mockBuilder(Builder.class); when(builder.build()).thenReturn(notification); return builder; } private NotificationController createNotificationController(Context context, Builder builder) { NotificationController controller = mock(NotificationController.class); when(controller.createNotificationBuilder()).thenReturn(builder); when(controller.getAccountName(account)).thenReturn(ACCOUNT_NAME); when(controller.getContext()).thenReturn(context); return controller; } private NotificationActionCreator createNotificationActionCreator() { return mock(NotificationActionCreator.class); } private Account createAccount() { Account account = mock(Account.class); when(account.getAccountNumber()).thenReturn(ACCOUNT_NUMBER); return account; } private MessagingController createMessagingController() { MessagingController messagingController = mock(MessagingController.class); when(messagingController.isMoveCapable(account)).thenReturn(true); return messagingController; } private NotificationContent createNotificationContent(MessageReference messageReference) { return new NotificationContent(messageReference, null, null, null, null, false); } private NotificationHolder createNotificationHolder(int notificationId, NotificationContent content) { return new NotificationHolder(notificationId, content); } private Notification createNotification() { return mock(Notification.class); } private MessageReference createMessageReference(int number) { return new MessageReference("account", "folder", String.valueOf(number), null); } private PendingIntent createFakePendingIntent(int requestCode) { return PendingIntent.getActivity(RuntimeEnvironment.application, requestCode, null, 0); } private ArrayList<MessageReference> createMessageReferenceList() { ArrayList<MessageReference> messageReferences = new ArrayList<MessageReference>(); messageReferences.add(createMessageReference(1)); messageReferences.add(createMessageReference(2)); return messageReferences; } private NotificationData createNotificationData(ArrayList<MessageReference> messageReferences) { NotificationData notificationData = mock(NotificationData.class); when(notificationData.getAccount()).thenReturn(account); when(notificationData.getAllMessageReferences()).thenReturn(messageReferences); return notificationData; } private Builder verifyExtendWasOnlyCalledOnce() { return verify(builder, times(1)).extend(any(Extender.class)); } private void verifyAddAction(int icon, String title, PendingIntent pendingIntent) { verify(builder).extend(action(icon, title, pendingIntent)); } private Builder verifyNumberOfActions(int expectedNumberOfActions) { return verify(builder).extend(numberOfActions(expectedNumberOfActions)); } private WearableExtender action(int icon, String title, PendingIntent pendingIntent) { return argThat(new ActionMatcher(icon, title, pendingIntent)); } private WearableExtender numberOfActions(int expectedNumberOfActions) { return argThat(new NumberOfActionsMatcher(expectedNumberOfActions)); } static class ActionMatcher extends ArgumentMatcher<WearableExtender> { private int icon; private String title; private PendingIntent pendingIntent; public ActionMatcher(int icon, String title, PendingIntent pendingIntent) { this.icon = icon; this.title = title; this.pendingIntent = pendingIntent; } @Override public boolean matches(Object argument) { if (!(argument instanceof WearableExtender)) { return false; } WearableExtender wearableExtender = (WearableExtender) argument; for (Action action : wearableExtender.getActions()) { if (action.icon == icon && action.title.equals(title) && action.actionIntent == pendingIntent) { return true; } } return false; } } static class NumberOfActionsMatcher extends ArgumentMatcher<WearableExtender> { private final int expectedNumberOfActions; public NumberOfActionsMatcher(int expectedNumberOfActions) { this.expectedNumberOfActions = expectedNumberOfActions; } @Override public boolean matches(Object argument) { if (!(argument instanceof WearableExtender)) { return false; } WearableExtender wearableExtender = (WearableExtender) argument; return wearableExtender.getActions().size() == expectedNumberOfActions; } } static class TestWearNotifications extends WearNotifications { private final MessagingController messagingController; public TestWearNotifications(NotificationController controller, NotificationActionCreator actionCreator, MessagingController messagingController) { super(controller, actionCreator); this.messagingController = messagingController; } @Override MessagingController createMessagingController() { return messagingController; } } }