Improve notification channel

This commit is contained in:
Oji Ofong 2018-11-10 09:40:46 -05:00
parent baa653ba3a
commit a1b3cc8f1f
19 changed files with 179 additions and 134 deletions

View file

@ -33,7 +33,7 @@ class AuthenticationErrorNotifications {
String text = resourceProvider.authenticationErrorBody(account.getDescription());
NotificationCompat.Builder builder = notificationHelper
.createNotificationBuilder(account, NotificationChannelUtils.Type.OTHER)
.createNotificationBuilder(account, NotificationChannelUtils.ChannelType.MISCELLANEOUS)
.setSmallIcon(resourceProvider.getIconWarning())
.setWhen(System.currentTimeMillis())
.setAutoCancel(true)

View file

@ -53,7 +53,8 @@ abstract class BaseNotifications {
}
protected NotificationCompat.Builder createAndInitializeNotificationBuilder(Account account) {
return notificationHelper.createNotificationBuilder(account, NotificationChannelUtils.Type.MESSAGES)
return notificationHelper.createNotificationBuilder(account,
NotificationChannelUtils.ChannelType.MESSAGES)
.setSmallIcon(getNewMailNotificationIcon())
.setColor(account.getChipColor())
.setWhen(System.currentTimeMillis())

View file

@ -32,7 +32,7 @@ class CertificateErrorNotifications {
String text = resourceProvider.certificateErrorBody();
NotificationCompat.Builder builder = notificationHelper
.createNotificationBuilder(account, NotificationChannelUtils.Type.OTHER)
.createNotificationBuilder(account, NotificationChannelUtils.ChannelType.MISCELLANEOUS)
.setSmallIcon(resourceProvider.getIconWarning())
.setWhen(System.currentTimeMillis())
.setAutoCancel(true)

View file

@ -6,7 +6,8 @@ import org.koin.dsl.module.applicationContext
val coreNotificationModule = applicationContext {
bean { NotificationController(get(), get(), get(), get(), get()) }
bean { NotificationManagerCompat.from(get()) }
bean { NotificationHelper(get(), get()) }
bean { NotificationHelper(get(), get(), get()) }
bean { NotificationChannelUtils(get(), get()) }
bean { CertificateErrorNotifications(get(), get(), get()) }
bean { AuthenticationErrorNotifications(get(), get(), get()) }
bean { SyncNotifications(get(), get(), get()) }

View file

@ -88,7 +88,8 @@ class LockScreenNotification {
int unreadCount = notificationData.getUnreadMessageCount();
String title = resourceProvider.newMessagesTitle(newMessages);
return notificationHelper.createNotificationBuilder(account, NotificationChannelUtils.Type.MESSAGES)
return notificationHelper.createNotificationBuilder(account,
NotificationChannelUtils.ChannelType.MESSAGES)
.setSmallIcon(resourceProvider.getIconNewMail())
.setColor(account.getChipColor())
.setNumber(unreadCount)

View file

@ -1,104 +0,0 @@
package com.fsck.k9.notification;
import android.app.NotificationChannel;
import android.app.NotificationChannelGroup;
import android.app.NotificationManager;
import android.content.Context;
import android.os.Build;
import android.support.annotation.RequiresApi;
import com.fsck.k9.Account;
import com.fsck.k9.Preferences;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
public class NotificationChannelUtils {
private static final String PREFIX_MESSAGES = "messages_";
private static final String PREFIX_OTHER = "other_";
public enum Type {
MESSAGES, OTHER
}
public static void updateChannels(Context context) {
if (Build.VERSION.SDK_INT < 26) {
return;
}
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
if (notificationManager != null) {
List<Account> accounts = Preferences.getPreferences(context).getAccounts();
removeChannelsForNonExistingOrChangedAccounts(notificationManager, accounts);
addChannelsForAccounts(context, notificationManager, accounts);
}
}
@RequiresApi(api = Build.VERSION_CODES.O)
private static void addChannelsForAccounts(Context context, NotificationManager notificationManager, List<Account> accounts) {
for (Account account : accounts) {
final String groupId = account.getUuid();
NotificationChannelGroup group = new NotificationChannelGroup(groupId, account.getDisplayName());
NotificationChannel channelNewMail = getChannelMessages(context, groupId);
NotificationChannel channelOther = getChannelOther(context, groupId);
channelNewMail.setGroup(groupId);
channelOther.setGroup(groupId);
notificationManager.createNotificationChannelGroup(group);
notificationManager.createNotificationChannel(channelNewMail);
notificationManager.createNotificationChannel(channelOther);
}
}
@RequiresApi(api = Build.VERSION_CODES.O)
private static void removeChannelsForNonExistingOrChangedAccounts(NotificationManager notificationManager, List<Account> accounts) {
HashMap<String, Account> existingAccounts = new HashMap<>();
for (Account account : accounts) {
existingAccounts.put(account.getUuid(), account);
}
List<NotificationChannelGroup> groups = notificationManager.getNotificationChannelGroups();
for (NotificationChannelGroup group : groups) {
final String groupId = group.getId();
boolean shouldDelete = false;
if (!existingAccounts.containsKey(groupId)) {
shouldDelete = true;
} else if (!existingAccounts.get(groupId).getName().equals(group.getName().toString())) {
// There is no way to change group names. Deleting group, so it is re-generated.
shouldDelete = true;
}
if (shouldDelete) {
notificationManager.deleteNotificationChannelGroup(groupId);
notificationManager.deleteNotificationChannel(PREFIX_MESSAGES + groupId);
notificationManager.deleteNotificationChannel(PREFIX_OTHER + groupId);
}
}
}
@RequiresApi(api = Build.VERSION_CODES.O)
private static NotificationChannel getChannelMessages(Context context, String accountId) {
NotificationChannel mChannel = new NotificationChannel(PREFIX_MESSAGES + accountId,
"Messages", NotificationManager.IMPORTANCE_HIGH);
mChannel.setDescription("When receiving new messages");
return mChannel;
}
@RequiresApi(api = Build.VERSION_CODES.O)
private static NotificationChannel getChannelOther(Context context, String accountId) {
NotificationChannel mChannel = new NotificationChannel(PREFIX_OTHER + accountId,
"Others", NotificationManager.IMPORTANCE_HIGH);
mChannel.setDescription("Other notifications like errors");
return mChannel;
}
public static String getChannelIdFor(Account account, Type type) {
if (type == Type.MESSAGES) {
return PREFIX_MESSAGES + account.getUuid();
} else {
return PREFIX_OTHER + account.getUuid();
}
}
}

View file

@ -0,0 +1,116 @@
package com.fsck.k9.notification
import android.app.NotificationChannel
import android.app.NotificationChannelGroup
import android.app.NotificationManager
import android.content.Context
import android.os.Build
import android.support.annotation.RequiresApi
import com.fsck.k9.Account
import com.fsck.k9.Preferences
class NotificationChannelUtils(private val context: Context, private val preferences: Preferences) {
enum class ChannelType {
MESSAGES, MISCELLANEOUS
}
fun updateChannels() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
return
}
val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE)
as NotificationManager
val accounts = preferences.accounts
removeChannelsForNonExistingOrChangedAccounts(notificationManager, accounts)
addChannelsForAccounts(notificationManager, accounts)
}
@RequiresApi(api = Build.VERSION_CODES.O)
private fun addChannelsForAccounts(
notificationManager: NotificationManager, accounts: List<Account>) {
for (account in accounts) {
val groupId = account.uuid
val group = NotificationChannelGroup(groupId, account.displayName)
val channelMessages = getChannelMessages(account)
val channelMiscellaneous = getChannelMiscellaneous(account)
notificationManager.createNotificationChannelGroup(group)
notificationManager.createNotificationChannel(channelMessages)
notificationManager.createNotificationChannel(channelMiscellaneous)
}
}
@RequiresApi(api = Build.VERSION_CODES.O)
private fun removeChannelsForNonExistingOrChangedAccounts(
notificationManager: NotificationManager, accounts: List<Account>) {
val existingAccounts = HashMap<String, Account>()
for (account in accounts) {
existingAccounts[account.uuid] = account
}
val groups = notificationManager.notificationChannelGroups
for (group in groups) {
val groupId = group.id
var shouldDelete = false
if (!existingAccounts.containsKey(groupId)) {
shouldDelete = true
} else if (existingAccounts[groupId]?.displayName != group.name.toString()) {
// There is no way to change group names. Deleting group, so it is re-generated.
shouldDelete = true
}
if (shouldDelete) {
notificationManager.deleteNotificationChannelGroup(groupId)
}
}
}
@RequiresApi(api = Build.VERSION_CODES.O)
private fun getChannelMessages(account: Account): NotificationChannel {
// TODO: Use String resource file to support language translations
val channelName = "Messages"
val channelDescription = "Notifications related to messages"
val channelId = getChannelIdFor(account, ChannelType.MESSAGES)
val importance = NotificationManager.IMPORTANCE_DEFAULT
val channelGroupId = account.uuid
val messagesChannel = NotificationChannel(channelId, channelName, importance)
messagesChannel.description = channelDescription
messagesChannel.group = channelGroupId
return messagesChannel
}
@RequiresApi(api = Build.VERSION_CODES.O)
private fun getChannelMiscellaneous(account: Account): NotificationChannel {
// TODO: Use String resource file to support language translations
val channelName = "Miscellaneous"
val channelDescription = "Miscellaneous notifications like errors etc."
val channelId = getChannelIdFor(account, ChannelType.MISCELLANEOUS)
val importance = NotificationManager.IMPORTANCE_DEFAULT
val channelGroupId = account.uuid
val miscellaneousChannel = NotificationChannel(channelId, channelName, importance)
miscellaneousChannel.description = channelDescription
miscellaneousChannel.group = channelGroupId
return miscellaneousChannel
}
fun getChannelIdFor(account: Account, channelType: ChannelType): String {
val accountUuid = account.uuid
return if (channelType == ChannelType.MESSAGES) {
"messages_channel_$accountUuid"
} else {
"miscellaneous_channel_$accountUuid"
}
}
}

View file

@ -10,10 +10,15 @@ import com.fsck.k9.K9
class NotificationHelper(
private val context: Context,
private val notificationManager: NotificationManagerCompat
private val notificationManager: NotificationManagerCompat,
private val channelUtils: NotificationChannelUtils
) {
fun configureNotification(builder: NotificationCompat.Builder, ringtone: String?, vibrationPattern: LongArray?,
ledColor: Int?, ledSpeed: Int, ringAndVibrate: Boolean) {
fun configureNotification(
builder: NotificationCompat.Builder,
ringtone: String?,
vibrationPattern: LongArray?,
ledColor: Int?, ledSpeed: Int,
ringAndVibrate: Boolean) {
if (K9.isQuietTime()) {
return
@ -57,8 +62,12 @@ class NotificationHelper(
return notificationManager
}
fun createNotificationBuilder(account: Account, type: NotificationChannelUtils.Type): NotificationCompat.Builder {
return NotificationCompat.Builder(context, NotificationChannelUtils.getChannelIdFor(account, type))
fun createNotificationBuilder(
account: Account,
channelType: NotificationChannelUtils.ChannelType
): NotificationCompat.Builder {
return NotificationCompat.Builder(context,
channelUtils.getChannelIdFor(account, channelType))
}

View file

@ -34,7 +34,7 @@ class SendFailedNotifications {
account, notificationId);
NotificationCompat.Builder builder = notificationHelper
.createNotificationBuilder(account, NotificationChannelUtils.Type.OTHER)
.createNotificationBuilder(account, NotificationChannelUtils.ChannelType.MISCELLANEOUS)
.setSmallIcon(resourceProvider.getIconWarning())
.setWhen(System.currentTimeMillis())
.setAutoCancel(true)

View file

@ -21,7 +21,7 @@ class SyncNotifications {
public SyncNotifications(NotificationHelper notificationHelper, NotificationActionCreator actionBuilder,
NotificationResourceProvider resourceProvider) {
NotificationResourceProvider resourceProvider) {
this.notificationHelper = notificationHelper;
this.actionBuilder = actionBuilder;
this.resourceProvider = resourceProvider;
@ -37,8 +37,8 @@ class SyncNotifications {
PendingIntent showMessageListPendingIntent = actionBuilder.createViewFolderPendingIntent(
account, outboxFolder, notificationId);
NotificationCompat.Builder builder = notificationHelper
.createNotificationBuilder(account, NotificationChannelUtils.Type.OTHER)
NotificationCompat.Builder builder = notificationHelper.createNotificationBuilder(account,
NotificationChannelUtils.ChannelType.MISCELLANEOUS)
.setSmallIcon(resourceProvider.getIconSendingMail())
.setWhen(System.currentTimeMillis())
.setOngoing(true)
@ -76,8 +76,8 @@ class SyncNotifications {
PendingIntent showMessageListPendingIntent = actionBuilder.createViewFolderPendingIntent(
account, folderServerId, notificationId);
NotificationCompat.Builder builder = notificationHelper
.createNotificationBuilder(account, NotificationChannelUtils.Type.OTHER)
NotificationCompat.Builder builder = notificationHelper.createNotificationBuilder(account,
NotificationChannelUtils.ChannelType.MISCELLANEOUS)
.setSmallIcon(resourceProvider.getIconCheckingMail())
.setWhen(System.currentTimeMillis())
.setOngoing(true)

View file

@ -14,6 +14,7 @@ import org.junit.Before;
import org.junit.Test;
import org.robolectric.RuntimeEnvironment;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@ -114,7 +115,10 @@ public class AuthenticationErrorNotificationsTest extends RobolectricTest {
NotificationHelper notificationHelper = mock(NotificationHelper.class);
when(notificationHelper.getContext()).thenReturn(RuntimeEnvironment.application);
when(notificationHelper.getNotificationManager()).thenReturn(notificationManager);
when(notificationHelper.createNotificationBuilder(mock(Account.class), NotificationChannelUtils.Type.OTHER)).thenReturn(builder);
when(notificationHelper.createNotificationBuilder(any(Account.class),
any(NotificationChannelUtils.ChannelType.class)))
.thenReturn(builder);
return notificationHelper;
}

View file

@ -112,7 +112,8 @@ public class BaseNotificationsTest {
private NotificationHelper createFakeNotificationHelper() {
Builder builder = MockHelper.mockBuilder(Builder.class);
NotificationHelper notificationHelper = mock(NotificationHelper.class);
when(notificationHelper.createNotificationBuilder(mock(Account.class), NotificationChannelUtils.Type.OTHER)).thenReturn(builder);
when(notificationHelper.createNotificationBuilder(any(Account.class), any(NotificationChannelUtils
.ChannelType.class))).thenReturn(builder);
when(notificationHelper.getAccountName(any(Account.class))).thenReturn(ACCOUNT_NAME);
return notificationHelper;
}

View file

@ -14,6 +14,7 @@ import org.junit.Before;
import org.junit.Test;
import org.robolectric.RuntimeEnvironment;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@ -114,7 +115,10 @@ public class CertificateErrorNotificationsTest extends RobolectricTest {
NotificationHelper notificationHelper = mock(NotificationHelper.class);
when(notificationHelper.getContext()).thenReturn(RuntimeEnvironment.application);
when(notificationHelper.getNotificationManager()).thenReturn(notificationManager);
when(notificationHelper.createNotificationBuilder(mock(Account.class), NotificationChannelUtils.Type.OTHER)).thenReturn(builder);
when(notificationHelper.createNotificationBuilder(any(Account.class),
any(NotificationChannelUtils.ChannelType.class)))
.thenReturn(builder);
return notificationHelper;
}

View file

@ -224,7 +224,8 @@ public class DeviceNotificationsTest extends RobolectricTest {
NotificationHelper notificationHelper = mock(NotificationHelper.class);
when(notificationHelper.getContext()).thenReturn(context);
when(notificationHelper.getAccountName(any(Account.class))).thenReturn(ACCOUNT_NAME);
when(notificationHelper.createNotificationBuilder(mock(Account.class), NotificationChannelUtils.Type.OTHER)).thenAnswer(new Answer<Builder>() {
when(notificationHelper.createNotificationBuilder(any(Account.class), any(NotificationChannelUtils
.ChannelType.class))).thenAnswer(new Answer<Builder>() {
private int invocationCount = 0;
@Override

View file

@ -168,7 +168,8 @@ public class LockScreenNotificationTest extends RobolectricTest {
NotificationHelper notificationHelper = mock(NotificationHelper.class);
when(notificationHelper.getContext()).thenReturn(context);
when(notificationHelper.getAccountName(any(Account.class))).thenReturn(ACCOUNT_NAME);
when(notificationHelper.createNotificationBuilder(mock(Account.class), NotificationChannelUtils.Type.OTHER)).thenReturn(builder);
when(notificationHelper.createNotificationBuilder(any(Account.class), any(NotificationChannelUtils
.ChannelType.class))).thenReturn(builder);
return notificationHelper;
}

View file

@ -91,7 +91,10 @@ public class SendFailedNotificationsTest extends RobolectricTest {
NotificationHelper notificationHelper = mock(NotificationHelper.class);
when(notificationHelper.getContext()).thenReturn(RuntimeEnvironment.application);
when(notificationHelper.getNotificationManager()).thenReturn(notificationManager);
when(notificationHelper.createNotificationBuilder(mock(Account.class), NotificationChannelUtils.Type.OTHER)).thenReturn(builder);
when(notificationHelper.createNotificationBuilder(any(Account.class),
any(NotificationChannelUtils.ChannelType.class)))
.thenReturn(builder);
return notificationHelper;
}

View file

@ -11,6 +11,7 @@ import com.fsck.k9.Account;
import com.fsck.k9.MockHelper;
import com.fsck.k9.RobolectricTest;
import com.fsck.k9.mail.Folder;
import org.junit.Before;
import org.junit.Test;
import org.robolectric.RuntimeEnvironment;
@ -117,13 +118,16 @@ public class SyncNotificationsTest extends RobolectricTest {
return builder;
}
private NotificationHelper createFakeNotificationHelper(NotificationManagerCompat notificationManager,
Builder builder) {
private NotificationHelper createFakeNotificationHelper(
NotificationManagerCompat notificationManager, Builder builder) {
NotificationHelper notificationHelper = mock(NotificationHelper.class);
when(notificationHelper.getContext()).thenReturn(RuntimeEnvironment.application);
when(notificationHelper.getNotificationManager()).thenReturn(notificationManager);
when(notificationHelper.createNotificationBuilder(mock(Account.class), NotificationChannelUtils.Type.OTHER)).thenReturn(builder);
when(notificationHelper.createNotificationBuilder(any(Account.class),
any(NotificationChannelUtils.ChannelType.class)))
.thenReturn(builder);
when(notificationHelper.getAccountName(any(Account.class))).thenReturn(ACCOUNT_NAME);
return notificationHelper;
}

View file

@ -1,8 +1,6 @@
package com.fsck.k9.notification;
import java.util.ArrayList;
import android.app.Notification;
import android.app.PendingIntent;
import android.content.Context;
@ -18,11 +16,14 @@ import com.fsck.k9.MockHelper;
import com.fsck.k9.RobolectricTest;
import com.fsck.k9.controller.MessageReference;
import com.fsck.k9.controller.MessagingController;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentMatcher;
import org.robolectric.RuntimeEnvironment;
import java.util.ArrayList;
import static org.junit.Assert.assertEquals;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.argThat;
@ -229,7 +230,8 @@ public class WearNotificationsTest extends RobolectricTest {
private NotificationHelper createNotificationHelper(Context context, Builder builder) {
NotificationHelper notificationHelper = mock(NotificationHelper.class);
when(notificationHelper.createNotificationBuilder(mock(Account.class), NotificationChannelUtils.Type.OTHER)).thenReturn(builder);
when(notificationHelper.createNotificationBuilder(any(Account.class), any(NotificationChannelUtils
.ChannelType.class))).thenReturn(builder);
when(notificationHelper.getAccountName(account)).thenReturn(ACCOUNT_NAME);
when(notificationHelper.getContext()).thenReturn(context);
return notificationHelper;
@ -347,7 +349,7 @@ public class WearNotificationsTest extends RobolectricTest {
private final MessagingController messagingController;
public TestWearNotifications(NotificationHelper notificationHelper, NotificationActionCreator actionCreator,
MessagingController messagingController, NotificationResourceProvider resourceProvider) {
MessagingController messagingController, NotificationResourceProvider resourceProvider) {
super(notificationHelper, actionCreator, resourceProvider);
this.messagingController = messagingController;
}

View file

@ -165,6 +165,7 @@ public class MessageList extends K9Activity implements MessageListFragmentListen
protected final SearchStatusManager searchStatusManager = DI.get(SearchStatusManager.class);
private StorageManager.StorageListener mStorageListener = new StorageListenerImplementation();
private final Preferences preferences = DI.get(Preferences.class);
private final NotificationChannelUtils channelUtils = DI.get(NotificationChannelUtils.class);
private ActionBar actionBar;
private ActionBarDrawerToggle drawerToggle;
@ -261,7 +262,7 @@ public class MessageList extends K9Activity implements MessageListFragmentListen
initializeLayout();
initializeFragments();
displayViews();
NotificationChannelUtils.updateChannels(this);
channelUtils.updateChannels();
ChangeLog cl = new ChangeLog(this);
if (cl.isFirstRun()) {