diff --git a/app/src/main/java/com/android/calendar/AllInOneActivity.java b/app/src/main/java/com/android/calendar/AllInOneActivity.java index 356a28803d..0b003f9563 100644 --- a/app/src/main/java/com/android/calendar/AllInOneActivity.java +++ b/app/src/main/java/com/android/calendar/AllInOneActivity.java @@ -267,12 +267,12 @@ protected void onCreate(Bundle icicle) { // This needs to be created before setContentView mController = CalendarController.getInstance(this); - // Create notification channel - AlertService.createChannels(this); - // Check and ask for most needed permissions checkAppPermissions(); + // Create notification channels + AlertService.createChannels(this); + // Get time from intent or icicle long timeMillis = -1; int viewType = -1; diff --git a/app/src/main/java/com/android/calendar/alerts/AlertReceiver.java b/app/src/main/java/com/android/calendar/alerts/AlertReceiver.java index a03258a770..4ed6e47674 100644 --- a/app/src/main/java/com/android/calendar/alerts/AlertReceiver.java +++ b/app/src/main/java/com/android/calendar/alerts/AlertReceiver.java @@ -17,8 +17,6 @@ package com.android.calendar.alerts; -import static com.android.calendar.alerts.AlertService.ALERT_CHANNEL_ID; - import android.app.Notification; import android.app.PendingIntent; import android.app.Service; @@ -230,10 +228,10 @@ private static PendingIntent createAlertActivityIntent(Context context) { } public static NotificationWrapper makeBasicNotification(Context context, String title, - String summaryText, long startMillis, long endMillis, long eventId, + String summaryText, long startMillis, long endMillis, long eventId, long calendarId, int notificationId, boolean doPopup, int priority) { Notification n = buildBasicNotification(new Notification.Builder(context), - context, title, summaryText, startMillis, endMillis, eventId, notificationId, + context, title, summaryText, startMillis, endMillis, eventId, calendarId, notificationId, doPopup, priority, false); return new NotificationWrapper(n, notificationId, eventId, startMillis, endMillis, doPopup); } @@ -247,7 +245,7 @@ public static boolean isResolveIntent(Context context, Intent intent) { private static Notification buildBasicNotification(Notification.Builder notificationBuilder, Context context, String title, String summaryText, long startMillis, long endMillis, - long eventId, int notificationId, boolean doPopup, int priority, + long eventId, long calendarId, int notificationId, boolean doPopup, int priority, boolean addActionButtons) { Resources resources = context.getResources(); if (title == null || title.length() == 0) { @@ -274,7 +272,7 @@ private static Notification buildBasicNotification(Notification.Builder notifica // Add setting channel ID for Oreo or later if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - notificationBuilder.setChannelId(ALERT_CHANNEL_ID); + notificationBuilder.setChannelId(UtilsKt.channelId(calendarId)); } if (doPopup) { @@ -346,10 +344,10 @@ private static Notification buildBasicNotification(Notification.Builder notifica */ public static NotificationWrapper makeExpandingNotification(Context context, String title, String summaryText, String description, long startMillis, long endMillis, long eventId, - int notificationId, boolean doPopup, int priority) { + long calendarId, int notificationId, boolean doPopup, int priority) { Notification.Builder basicBuilder = new Notification.Builder(context); Notification notification = buildBasicNotification(basicBuilder, context, title, - summaryText, startMillis, endMillis, eventId, notificationId, doPopup, + summaryText, startMillis, endMillis, eventId, calendarId, notificationId, doPopup, priority, true); // Create a new-style expanded notification diff --git a/app/src/main/java/com/android/calendar/alerts/AlertService.java b/app/src/main/java/com/android/calendar/alerts/AlertService.java index 410d306b6b..7933429a7c 100644 --- a/app/src/main/java/com/android/calendar/alerts/AlertService.java +++ b/app/src/main/java/com/android/calendar/alerts/AlertService.java @@ -70,7 +70,7 @@ */ public class AlertService extends Service { - public static final String ALERT_CHANNEL_ID = "alert_channel_01"; + public static final String ALERT_CHANNEL_GROUP_ID = "alert_channel_group_01"; public static final String FOREGROUND_CHANNEL_ID = "foreground_channel_01"; // Hard limit to the number of notifications displayed. @@ -89,6 +89,7 @@ public class AlertService extends Service { CalendarAlerts.BEGIN, // 9 CalendarAlerts.END, // 10 CalendarAlerts.DESCRIPTION, // 11 + CalendarAlerts.CALENDAR_ID, // 12 }; private static final String TAG = "AlertService"; private static final int ALERT_INDEX_ID = 0; @@ -103,6 +104,7 @@ public class AlertService extends Service { private static final int ALERT_INDEX_BEGIN = 9; private static final int ALERT_INDEX_END = 10; private static final int ALERT_INDEX_DESCRIPTION = 11; + private static final int ALERT_INDEX_CALENDAR_ID = 12; private static final String ACTIVE_ALERTS_SELECTION = "(" + CalendarAlerts.STATE + "=? OR " + CalendarAlerts.STATE + "=?) AND " + CalendarAlerts.ALARM_TIME + "<="; private static final String[] ACTIVE_ALERTS_SELECTION_ARGS = new String[] { @@ -282,7 +284,7 @@ public static boolean generateAlerts(Context context, NotificationMgr nm, String summaryText = AlertUtils.formatTimeLocation(context, info.startMillis, info.allDay, info.location); notification = AlertReceiver.makeBasicNotification(context, info.eventName, - summaryText, info.startMillis, info.endMillis, info.eventId, + summaryText, info.startMillis, info.endMillis, info.eventId, info.calendarId, AlertUtils.EXPIRED_GROUP_NOTIFICATION_ID, false, Notification.PRIORITY_MIN); } else { @@ -473,6 +475,7 @@ static int processQuery(final Cursor alertCursor, final Context context, while (alertCursor.moveToNext()) { final long alertId = alertCursor.getLong(ALERT_INDEX_ID); final long eventId = alertCursor.getLong(ALERT_INDEX_EVENT_ID); + final long calendarId = alertCursor.getLong(ALERT_INDEX_CALENDAR_ID); final int minutes = alertCursor.getInt(ALERT_INDEX_MINUTES); final String eventName = alertCursor.getString(ALERT_INDEX_TITLE); final String description = alertCursor.getString(ALERT_INDEX_DESCRIPTION); @@ -511,6 +514,7 @@ static int processQuery(final Cursor alertCursor, final Context context, msgBuilder.append("alertCursor result: alarmTime:").append(alarmTime) .append(" alertId:").append(alertId) .append(" eventId:").append(eventId) + .append(" calendarId:").append(calendarId) .append(" state: ").append(state) .append(" minutes:").append(minutes) .append(" declined:").append(declined) @@ -590,7 +594,7 @@ static int processQuery(final Cursor alertCursor, final Context context, // TODO: Prefer accepted events in case of ties. NotificationInfo newInfo = new NotificationInfo(eventName, location, - description, beginTime, endTime, eventId, allDay, newAlert); + description, beginTime, endTime, eventId, calendarId, allDay, newAlert); // Adjust for all day events to ensure the right bucket. Don't use the 1/4 event // duration grace period for these. @@ -707,8 +711,8 @@ private static void postNotification(NotificationInfo info, String summaryText, String tickerText = getTickerText(info.eventName, info.location); NotificationWrapper notification = AlertReceiver.makeExpandingNotification(context, - info.eventName, summaryText, info.description, info.startMillis, - info.endMillis, info.eventId, notificationId, prefs.getDoPopup(), priorityVal); + info.eventName, summaryText, info.description, info.startMillis, info.endMillis, + info.eventId, info.calendarId, notificationId, prefs.getDoPopup(), priorityVal); boolean quietUpdate = true; String ringtone = NotificationPrefs.EMPTY_RINGTONE; @@ -967,16 +971,13 @@ public IBinder onBind(Intent intent) { public static void createChannels(Context context) { if (Utils.isOreoOrLater()) { - // Create notification channel - NotificationMgr nm = new NotificationMgrWrapper( - (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE)); + NotificationManager nm = + (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); - NotificationChannel channel = new NotificationChannel( - ALERT_CHANNEL_ID, - context.getString(R.string.standalone_app_label), - NotificationManager.IMPORTANCE_HIGH); - channel.enableLights(true); + // Create a channel per calendar (so that the user can turn it off with granularity) + UtilsKt.createPerCalendarChannels(context, nm); + // Create a "Background tasks" channel to keep the app alive NotificationChannel foregroundChannel = new NotificationChannel( FOREGROUND_CHANNEL_ID, context.getString(R.string.foreground_notification_channel_name), @@ -984,7 +985,6 @@ public static void createChannels(Context context) { foregroundChannel.setDescription( context.getString(R.string.foreground_notification_channel_description)); - nm.createNotificationChannel(channel); nm.createNotificationChannel(foregroundChannel); } } @@ -1056,17 +1056,19 @@ static class NotificationInfo { long startMillis; long endMillis; long eventId; + long calendarId; boolean allDay; boolean newAlert; NotificationInfo(String eventName, String location, String description, long startMillis, - long endMillis, long eventId, boolean allDay, boolean newAlert) { + long endMillis, long eventId, long calendarId, boolean allDay, boolean newAlert) { this.eventName = eventName; this.location = location; this.description = description; this.startMillis = startMillis; this.endMillis = endMillis; this.eventId = eventId; + this.calendarId = calendarId; this.newAlert = newAlert; this.allDay = allDay; } diff --git a/app/src/main/java/com/android/calendar/alerts/Utils.kt b/app/src/main/java/com/android/calendar/alerts/Utils.kt new file mode 100644 index 0000000000..30ba68d187 --- /dev/null +++ b/app/src/main/java/com/android/calendar/alerts/Utils.kt @@ -0,0 +1,78 @@ +package com.android.calendar.alerts + +import android.app.NotificationChannel +import android.app.NotificationChannelGroup +import android.app.NotificationManager +import android.content.Context +import android.os.Build +import android.provider.CalendarContract +import androidx.annotation.RequiresApi +import androidx.core.database.getStringOrNull +import com.android.calendar.Utils +import com.android.calendar.alerts.AlertService.ALERT_CHANNEL_GROUP_ID +import ws.xsoh.etar.R + + +val PROJECTION = arrayOf( + CalendarContract.Calendars._ID, + CalendarContract.Calendars.CALENDAR_DISPLAY_NAME, +) + +data class CalendarChannel(val id: Long, val displayName: String?) + +fun channelId(id: Long) = "calendar$id" + +@RequiresApi(Build.VERSION_CODES.O) +fun createPerCalendarChannels(context: Context, nm: NotificationManager) { + val calendars: MutableList = mutableListOf() + + // Make sure we have the right permissions to access the calendar list + if (!Utils.isCalendarPermissionGranted(context, false)) return + + context.contentResolver.query( + CalendarContract.Calendars.CONTENT_URI, + PROJECTION, + null, + null, + CalendarContract.Calendars.ACCOUNT_NAME + )?.use { + while (it.moveToNext()) { + val id = it.getLong(PROJECTION.indexOf(CalendarContract.Calendars._ID)) + val displayName = + it.getStringOrNull(PROJECTION.indexOf(CalendarContract.Calendars.CALENDAR_DISPLAY_NAME)) + + calendars.add(CalendarChannel(id, displayName)) + } + } + + // Make NotificationChannel group for calendars + nm.createNotificationChannelGroup( + NotificationChannelGroup( + ALERT_CHANNEL_GROUP_ID, context.getString(R.string.calendars) + ) + ) + + // Fetch list of existing notification channels + val toDelete = nm.notificationChannels.filter { channel: NotificationChannel -> + // Only consider the channels of the calendar group + channel.group == ALERT_CHANNEL_GROUP_ID + // And only keep those that don't correspond to calendars (so those we want to delete) + && !calendars.any { channelId(it.id) == channel.id } + } + + // We want to delete these channels because they don't correspond to any calendars (anymore) + toDelete.forEach { nm.deleteNotificationChannel(it.id) } + + val channels = calendars.map { + NotificationChannel( + channelId(it.id), + if (it.displayName.isNullOrBlank()) context.getString(R.string.preferences_calendar_no_display_name) else it.displayName, + NotificationManager.IMPORTANCE_HIGH + ).apply { + enableLights(true) + group = ALERT_CHANNEL_GROUP_ID + } + } + + channels.forEach { nm.createNotificationChannel(it) } +} diff --git a/app/src/main/java/com/android/calendar/settings/CalendarPreferences.kt b/app/src/main/java/com/android/calendar/settings/CalendarPreferences.kt index 2a17ce9b5f..4743d602d1 100644 --- a/app/src/main/java/com/android/calendar/settings/CalendarPreferences.kt +++ b/app/src/main/java/com/android/calendar/settings/CalendarPreferences.kt @@ -25,6 +25,7 @@ import android.content.pm.PackageManager import android.graphics.drawable.Drawable import android.os.Bundle import android.provider.CalendarContract +import android.provider.Settings import android.util.TypedValue import androidx.appcompat.app.AlertDialog import androidx.core.content.ContextCompat @@ -34,6 +35,7 @@ import androidx.preference.PreferenceCategory import androidx.preference.PreferenceFragmentCompat import androidx.preference.SwitchPreference import com.android.calendar.Utils +import com.android.calendar.alerts.channelId import com.android.calendar.persistence.CalendarRepository import ws.xsoh.etar.R @@ -133,11 +135,23 @@ class CalendarPreferences : PreferenceFragmentCompat() { isSelectable = false } - if (!isLocalAccount) { screen.addPreference(synchronizePreference) } + screen.addPreference(visiblePreference) + + if(Utils.isOreoOrLater()){ + val notificationPreference = Preference(context).apply { + title = getString(R.string.preferences_manage_notifications) + intent = Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS).apply { + putExtra(Settings.EXTRA_APP_PACKAGE, context.packageName) + putExtra(Settings.EXTRA_CHANNEL_ID, channelId(this@CalendarPreferences.calendarId)) + } + } + screen.addPreference(notificationPreference) + } + screen.addPreference(colorPreference) if (isLocalAccount) { screen.addPreference(displayNamePreference) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 0b0baf56de..34d50e494c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -779,6 +779,8 @@ Calendar information Color Display events + + Manage notifications Synchronize this calendar Warning Changing the color may be reverted when the calendar is synchronized again.\n\nIn DAVx⁵ \'Manage calendar colors\' can be disabled to prevent this. @@ -805,4 +807,7 @@ light + + Calendars +