Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 22b6cf2f authored by Michael W's avatar Michael W
Browse files

DeskClock: Rework notification channel implementation



* Alarm notifications etc. are important and should be on high
  importance level.
* On contrast, upcoming alarms etc. should not have sounds or vibrations
  attached to them.
* In addition, we need to fix strings for notification channel.
* In order to achieve this, we need to create some new channels because
  you can't change the priority for existing channels
* Delete old channels on boot and update the names of existing channels if
  they already exist (they get created with the first notification
  requiring them)
* Move creation of upcoming alarm notifications into one place

Co-authored-by: default avatarWang Han <416810799@qq.com>
Change-Id: I6d2e9abd6a822a62b3313c62b0617d8d9211948e
parent c3450b4f
Loading
Loading
Loading
Loading
+7 −0
Original line number Diff line number Diff line
@@ -30,4 +30,11 @@
        <item>Nothing</item>
    </string-array>

    <!-- Strings for notification channel. -->
    <string name="firing_alarms_timers_channel">Firing alarms &amp; timers</string>
    <string name="alarm_missed_channel">Missed alarms</string>
    <string name="alarm_snooze_channel">Snoozed alarms</string>
    <string name="alarm_upcoming_channel">Upcoming alarms</string>
    <string name="stopwatch_channel">Stopwatch</string>
    <string name="timer_channel">Timer</string>
</resources>
+2 −0
Original line number Diff line number Diff line
@@ -27,6 +27,7 @@ import com.android.deskclock.alarms.AlarmStateManager;
import com.android.deskclock.alarms.AlarmNotifications;
import com.android.deskclock.controller.Controller;
import com.android.deskclock.data.DataModel;
import com.android.deskclock.NotificationUtils;
import com.android.deskclock.provider.AlarmInstance;

import java.util.Calendar;
@@ -90,6 +91,7 @@ public class AlarmInitReceiver extends BroadcastReceiver {
        if (Intent.ACTION_BOOT_COMPLETED.equals(action)
                || Intent.ACTION_LOCALE_CHANGED.equals(action)) {
            Controller.getController().updateShortcuts();
            NotificationUtils.updateNotificationChannels(context);
        }

        // Notifications are canceled by the system on application upgrade. This broadcast signals
+155 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2020 The LineageOS 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.deskclock;

import static androidx.core.app.NotificationManagerCompat.IMPORTANCE_DEFAULT;
import static androidx.core.app.NotificationManagerCompat.IMPORTANCE_HIGH;
import static androidx.core.app.NotificationManagerCompat.IMPORTANCE_LOW;

import android.app.NotificationChannel;
import android.content.Context;
import android.util.ArraySet;
import android.util.Log;
import androidx.core.app.NotificationManagerCompat;

import com.android.deskclock.Utils;
import com.android.deskclock.alarms.AlarmNotifications;
import com.android.deskclock.data.StopwatchNotificationBuilder;
import com.android.deskclock.data.TimerNotificationBuilder;

import java.util.HashMap;
import java.util.Map;
import java.util.Set;

public class NotificationUtils {

    private static final String TAG = NotificationUtils.class.getSimpleName();

    /**
     * Notification channel containing all missed alarm notifications.
     */
    public static final String ALARM_MISSED_NOTIFICATION_CHANNEL_ID = "alarmMissedNotification";

    /**
     * Notification channel containing all upcoming alarm notifications.
     */
    public static final String ALARM_UPCOMING_NOTIFICATION_CHANNEL_ID = "alarmUpcomingNotification";

    /**
     * Notification channel containing all snooze notifications.
     */
    public static final String ALARM_SNOOZE_NOTIFICATION_CHANNEL_ID = "alarmSnoozeNotification";

    /**
     * Notification channel containing all firing alarm and timer notifications.
     */
    public static final String FIRING_NOTIFICATION_CHANNEL_ID = "firingAlarmsTimersNotification";

    /**
     * Notification channel containing all TimerModel notifications.
     */
    public static final String TIMER_MODEL_NOTIFICATION_CHANNEL_ID = "timerNotification";

    /**
     * Notification channel containing all stopwatch notifications.
     */
    public static final String STOPWATCH_NOTIFICATION_CHANNEL_ID = "stopwatchNotification";

    private static Map<String, int[]> CHANNEL_PROPS = new HashMap<String, int[]>();
    static {
        CHANNEL_PROPS.put(ALARM_MISSED_NOTIFICATION_CHANNEL_ID, new int[]{
                R.string.alarm_missed_channel,
                IMPORTANCE_HIGH
        });
        CHANNEL_PROPS.put(ALARM_SNOOZE_NOTIFICATION_CHANNEL_ID, new int[]{
                R.string.alarm_snooze_channel,
                IMPORTANCE_LOW
        });
        CHANNEL_PROPS.put(ALARM_UPCOMING_NOTIFICATION_CHANNEL_ID, new int[]{
                R.string.alarm_upcoming_channel,
                IMPORTANCE_LOW
        });
        CHANNEL_PROPS.put(FIRING_NOTIFICATION_CHANNEL_ID, new int[]{
                R.string.firing_alarms_timers_channel,
                IMPORTANCE_HIGH
        });
        CHANNEL_PROPS.put(STOPWATCH_NOTIFICATION_CHANNEL_ID, new int[]{
                R.string.stopwatch_channel,
                IMPORTANCE_LOW
        });
        CHANNEL_PROPS.put(TIMER_MODEL_NOTIFICATION_CHANNEL_ID, new int[]{
                R.string.timer_channel,
                IMPORTANCE_LOW
        });
    }

    public static void createChannel(Context context, String id) {
        if (!Utils.isOOrLater()) {
            return;
        }

        if (!CHANNEL_PROPS.containsKey(id)) {
            Log.e(TAG, "Invalid channel requested: " + id);
            return;
        }

        int[] properties = (int[]) CHANNEL_PROPS.get(id);
        int nameId = properties[0];
        int importance = properties[1];
        NotificationChannel channel = new NotificationChannel(
                id, context.getString(nameId), importance);
        NotificationManagerCompat nm = NotificationManagerCompat.from(context);
        nm.createNotificationChannel(channel);
    }

    private static void deleteChannel(NotificationManagerCompat nm, String channelId) {
        NotificationChannel channel = nm.getNotificationChannel(channelId);
        if (channel != null) {
            nm.deleteNotificationChannel(channelId);
        }
    }

    private static Set<String> getAllExistingChannelIds(NotificationManagerCompat nm) {
        Set<String> result = new ArraySet<>();
        for (NotificationChannel channel : nm.getNotificationChannels()) {
            result.add(channel.getId());
        }
        return result;
    }

    public static void updateNotificationChannels(Context context) {
        if (!Utils.isOOrLater()) {
            return;
        }

        NotificationManagerCompat nm = NotificationManagerCompat.from(context);

        // These channels got a new behavior so we need to recreate them with new ids
        deleteChannel(nm, "alarmLowPriorityNotification");
        deleteChannel(nm, "alarmHighPriorityNotification");
        deleteChannel(nm, "StopwatchNotification");
        deleteChannel(nm, "alarmNotification");
        deleteChannel(nm, "TimerModelNotification");

        // We recreate all existing channels so any language change or our name changes propagate
        // to the actual channels
        Set<String> existingChannelIds = getAllExistingChannelIds(nm);
        for (String id : existingChannelIds) {
            createChannel(context, id);
        }
    }
}
+7 −0
Original line number Diff line number Diff line
@@ -156,6 +156,13 @@ public class Utils {
        return BuildCompat.isAtLeastNMR1();
    }

    /**
     * @return {@code true} if the device is {@link Build.VERSION_CODES#O} or later
     */
    public static boolean isOOrLater() {
        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O;
    }

    /**
     * @param resourceId identifies an application resource
     * @return the Uri by which the application resource is accessed
+35 −142
Original line number Diff line number Diff line
@@ -15,6 +15,11 @@
 */
package com.android.deskclock.alarms;

import static com.android.deskclock.NotificationUtils.ALARM_MISSED_NOTIFICATION_CHANNEL_ID;
import static com.android.deskclock.NotificationUtils.ALARM_SNOOZE_NOTIFICATION_CHANNEL_ID;
import static com.android.deskclock.NotificationUtils.ALARM_UPCOMING_NOTIFICATION_CHANNEL_ID;
import static com.android.deskclock.NotificationUtils.FIRING_NOTIFICATION_CHANNEL_ID;

import android.annotation.TargetApi;
import android.app.Notification;
import android.app.NotificationChannel;
@@ -34,6 +39,7 @@ import com.android.deskclock.AlarmClockFragment;
import com.android.deskclock.AlarmUtils;
import com.android.deskclock.DeskClock;
import com.android.deskclock.LogUtils;
import com.android.deskclock.NotificationUtils;
import com.android.deskclock.R;
import com.android.deskclock.Utils;
import com.android.deskclock.provider.Alarm;
@@ -47,35 +53,6 @@ import java.util.Objects;
public final class AlarmNotifications {
    static final String EXTRA_NOTIFICATION_ID = "extra_notification_id";

    /**
     * Notification channel containing all low priority notifications.
     */
    private static final String ALARM_LOW_PRIORITY_NOTIFICATION_CHANNEL_ID =
            "alarmLowPriorityNotification";

    /**
     * Notification channel containing all high priority notifications.
     */
    private static final String ALARM_HIGH_PRIORITY_NOTIFICATION_CHANNEL_ID =
            "alarmHighPriorityNotification";

    /**
     * Notification channel containing all snooze notifications.
     */
    private static final String ALARM_SNOOZE_NOTIFICATION_CHANNEL_ID =
            "alarmSnoozeNotification";

    /**
     * Notification channel containing all missed notifications.
     */
    private static final String ALARM_MISSED_NOTIFICATION_CHANNEL_ID =
            "alarmMissedNotification";

    /**
     * Notification channel containing all alarm notifications.
     */
    private static final String ALARM_NOTIFICATION_CHANNEL_ID = "alarmNotification";

    /**
     * Formats times such that chronological order and lexicographical order agree.
     */
@@ -112,12 +89,13 @@ public final class AlarmNotifications {
     */
    private static final int ALARM_FIRING_NOTIFICATION_ID = Integer.MAX_VALUE - 7;

    static synchronized void showLowPriorityNotification(Context context,
            AlarmInstance instance) {
        LogUtils.v("Displaying low priority notification for alarm instance: " + instance.mId);
    static synchronized void showUpcomingNotification(Context context,
            AlarmInstance instance, boolean lowPriority) {
        LogUtils.v("Displaying upcoming alarm notification for alarm instance: " + instance.mId +
                "low priority: " + lowPriority);

        NotificationCompat.Builder builder = new NotificationCompat.Builder(
                 context, ALARM_LOW_PRIORITY_NOTIFICATION_CHANNEL_ID)
                 context, ALARM_UPCOMING_NOTIFICATION_CHANNEL_ID)
                         .setShowWhen(false)
                        .setContentTitle(context.getString(
                                R.string.alarm_alert_predismiss_title))
@@ -127,7 +105,7 @@ public final class AlarmNotifications {
                        .setSmallIcon(R.drawable.stat_notify_alarm)
                        .setAutoCancel(false)
                        .setSortKey(createSortKey(instance))
                        .setPriority(NotificationCompat.PRIORITY_DEFAULT)
                        .setPriority(NotificationCompat.PRIORITY_LOW)
                        .setCategory(NotificationCompat.CATEGORY_EVENT)
                        .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
                        .setLocalOnly(true);
@@ -136,68 +114,20 @@ public final class AlarmNotifications {
            builder.setGroup(UPCOMING_GROUP_KEY);
        }

        final int id = instance.hashCode();
        if (lowPriority) {
            // Setup up hide notification
            Intent hideIntent = AlarmStateManager.createStateChangeIntent(context,
                    AlarmStateManager.ALARM_DELETE_TAG, instance,
                    AlarmInstance.HIDE_NOTIFICATION_STATE);
        final int id = instance.hashCode();

            builder.setDeleteIntent(PendingIntent.getService(context, id,
                    hideIntent, PendingIntent.FLAG_UPDATE_CURRENT));

        // Setup up dismiss action
        Intent dismissIntent = AlarmStateManager.createStateChangeIntent(context,
                AlarmStateManager.ALARM_DISMISS_TAG, instance, AlarmInstance.PREDISMISSED_STATE);
        builder.addAction(R.drawable.ic_alarm_off_24dp,
                context.getString(R.string.alarm_alert_dismiss_text),
                PendingIntent.getService(context, id,
                        dismissIntent, PendingIntent.FLAG_UPDATE_CURRENT));

        // Setup content action if instance is owned by alarm
        Intent viewAlarmIntent = createViewAlarmIntent(context, instance);
        builder.setContentIntent(PendingIntent.getActivity(context, id,
                viewAlarmIntent, PendingIntent.FLAG_UPDATE_CURRENT));

        NotificationManagerCompat nm = NotificationManagerCompat.from(context);
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
            NotificationChannel channel = new NotificationChannel(
                    ALARM_LOW_PRIORITY_NOTIFICATION_CHANNEL_ID,
                    context.getString(R.string.default_label),
                    NotificationManagerCompat.IMPORTANCE_DEFAULT);
            nm.createNotificationChannel(channel);
        }
        final Notification notification = builder.build();
        nm.notify(id, notification);
        updateUpcomingAlarmGroupNotification(context, -1, notification);
    }

    static synchronized void showHighPriorityNotification(Context context,
            AlarmInstance instance) {
        LogUtils.v("Displaying high priority notification for alarm instance: " + instance.mId);

        NotificationCompat.Builder builder = new NotificationCompat.Builder(
                context, ALARM_HIGH_PRIORITY_NOTIFICATION_CHANNEL_ID)
                        .setShowWhen(false)
                        .setContentTitle(context.getString(
                                R.string.alarm_alert_predismiss_title))
                        .setContentText(AlarmUtils.getAlarmText(
                                context, instance, true /* includeLabel */))
                        .setColor(ContextCompat.getColor(context, R.color.default_background))
                        .setSmallIcon(R.drawable.stat_notify_alarm)
                        .setAutoCancel(false)
                        .setSortKey(createSortKey(instance))
                        .setPriority(NotificationCompat.PRIORITY_HIGH)
                        .setCategory(NotificationCompat.CATEGORY_EVENT)
                        .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
                        .setLocalOnly(true);

        if (Utils.isNOrLater()) {
            builder.setGroup(UPCOMING_GROUP_KEY);
        }

        // Setup up dismiss action
        Intent dismissIntent = AlarmStateManager.createStateChangeIntent(context,
                AlarmStateManager.ALARM_DISMISS_TAG, instance, AlarmInstance.PREDISMISSED_STATE);
        final int id = instance.hashCode();
        builder.addAction(R.drawable.ic_alarm_off_24dp,
                context.getString(R.string.alarm_alert_dismiss_text),
                PendingIntent.getService(context, id,
@@ -209,13 +139,7 @@ public final class AlarmNotifications {
                viewAlarmIntent, PendingIntent.FLAG_UPDATE_CURRENT));

        NotificationManagerCompat nm = NotificationManagerCompat.from(context);
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
            NotificationChannel channel = new NotificationChannel(
                    ALARM_HIGH_PRIORITY_NOTIFICATION_CHANNEL_ID,
                    context.getString(R.string.default_label),
                    NotificationManagerCompat.IMPORTANCE_DEFAULT);
            nm.createNotificationChannel(channel);
        }
        NotificationUtils.createChannel(context, ALARM_UPCOMING_NOTIFICATION_CHANNEL_ID);
        final Notification notification = builder.build();
        nm.notify(id, notification);
        updateUpcomingAlarmGroupNotification(context, -1, notification);
@@ -281,14 +205,6 @@ public final class AlarmNotifications {
        }

        final NotificationManagerCompat nm = NotificationManagerCompat.from(context);
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
            NotificationChannel channel = new NotificationChannel(
                    ALARM_NOTIFICATION_CHANNEL_ID,
                    context.getString(R.string.default_label),
                    NotificationManagerCompat.IMPORTANCE_DEFAULT);
            nm.createNotificationChannel(channel);
        }

        final Notification firstUpcoming = getFirstActiveNotification(context, UPCOMING_GROUP_KEY,
                canceledNotificationId, postedNotification);
        if (firstUpcoming == null) {
@@ -299,14 +215,16 @@ public final class AlarmNotifications {
        Notification summary = getActiveGroupSummaryNotification(context, UPCOMING_GROUP_KEY);
        if (summary == null
                || !Objects.equals(summary.contentIntent, firstUpcoming.contentIntent)) {
            summary = new NotificationCompat.Builder(context, ALARM_NOTIFICATION_CHANNEL_ID)
            NotificationUtils.createChannel(context, ALARM_UPCOMING_NOTIFICATION_CHANNEL_ID);
            summary = new NotificationCompat.Builder(context,
                        ALARM_UPCOMING_NOTIFICATION_CHANNEL_ID)
                    .setShowWhen(false)
                    .setContentIntent(firstUpcoming.contentIntent)
                    .setColor(ContextCompat.getColor(context, R.color.default_background))
                    .setSmallIcon(R.drawable.stat_notify_alarm)
                    .setGroup(UPCOMING_GROUP_KEY)
                    .setGroupSummary(true)
                    .setPriority(NotificationCompat.PRIORITY_HIGH)
                    .setPriority(NotificationCompat.PRIORITY_LOW)
                    .setCategory(NotificationCompat.CATEGORY_EVENT)
                    .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
                    .setLocalOnly(true)
@@ -322,14 +240,6 @@ public final class AlarmNotifications {
        }

        final NotificationManagerCompat nm = NotificationManagerCompat.from(context);
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
            NotificationChannel channel = new NotificationChannel(
                    ALARM_NOTIFICATION_CHANNEL_ID,
                    context.getString(R.string.default_label),
                    NotificationManagerCompat.IMPORTANCE_DEFAULT);
            nm.createNotificationChannel(channel);
        }

        final Notification firstMissed = getFirstActiveNotification(context, MISSED_GROUP_KEY,
                canceledNotificationId, postedNotification);
        if (firstMissed == null) {
@@ -340,14 +250,8 @@ public final class AlarmNotifications {
        Notification summary = getActiveGroupSummaryNotification(context, MISSED_GROUP_KEY);
        if (summary == null
                || !Objects.equals(summary.contentIntent, firstMissed.contentIntent)) {
            if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
                NotificationChannel channel = new NotificationChannel(
                        ALARM_MISSED_NOTIFICATION_CHANNEL_ID,
                        context.getString(R.string.default_label),
                        NotificationManagerCompat.IMPORTANCE_DEFAULT);
                nm.createNotificationChannel(channel);
            }
            summary = new NotificationCompat.Builder(context, ALARM_NOTIFICATION_CHANNEL_ID)
            NotificationUtils.createChannel(context, ALARM_MISSED_NOTIFICATION_CHANNEL_ID);
            summary = new NotificationCompat.Builder(context, ALARM_MISSED_NOTIFICATION_CHANNEL_ID)
                    .setShowWhen(false)
                    .setContentIntent(firstMissed.contentIntent)
                    .setColor(ContextCompat.getColor(context, R.color.default_background))
@@ -377,7 +281,7 @@ public final class AlarmNotifications {
                        .setSmallIcon(R.drawable.stat_notify_alarm)
                        .setAutoCancel(false)
                        .setSortKey(createSortKey(instance))
                        .setPriority(NotificationCompat.PRIORITY_MAX)
                        .setPriority(NotificationCompat.PRIORITY_LOW)
                        .setCategory(NotificationCompat.CATEGORY_EVENT)
                        .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
                        .setLocalOnly(true);
@@ -401,13 +305,7 @@ public final class AlarmNotifications {
                viewAlarmIntent, PendingIntent.FLAG_UPDATE_CURRENT));

        NotificationManagerCompat nm = NotificationManagerCompat.from(context);
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
            NotificationChannel channel = new NotificationChannel(
                    ALARM_SNOOZE_NOTIFICATION_CHANNEL_ID,
                    context.getString(R.string.default_label),
                    NotificationManagerCompat.IMPORTANCE_DEFAULT);
            nm.createNotificationChannel(channel);
        }
        NotificationUtils.createChannel(context, ALARM_SNOOZE_NOTIFICATION_CHANNEL_ID);
        final Notification notification = builder.build();
        nm.notify(id, notification);
        updateUpcomingAlarmGroupNotification(context, -1, notification);
@@ -454,13 +352,7 @@ public final class AlarmNotifications {
                showAndDismiss, PendingIntent.FLAG_UPDATE_CURRENT));

        NotificationManagerCompat nm = NotificationManagerCompat.from(context);
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
            NotificationChannel channel = new NotificationChannel(
                    ALARM_MISSED_NOTIFICATION_CHANNEL_ID,
                    context.getString(R.string.default_label),
                    NotificationManagerCompat.IMPORTANCE_DEFAULT);
            nm.createNotificationChannel(channel);
        }
        NotificationUtils.createChannel(context, ALARM_MISSED_NOTIFICATION_CHANNEL_ID);
        final Notification notification = builder.build();
        nm.notify(id, notification);
        updateMissedAlarmGroupNotification(context, -1, notification);
@@ -471,7 +363,7 @@ public final class AlarmNotifications {

        Resources resources = service.getResources();
        NotificationCompat.Builder notification = new NotificationCompat.Builder(
                service, ALARM_NOTIFICATION_CHANNEL_ID)
                service, FIRING_NOTIFICATION_CHANNEL_ID)
                        .setContentTitle(instance.getLabelOrDefault(service))
                        .setContentText(AlarmUtils.getFormattedTime(
                                service, instance.getAlarmTime()))
@@ -520,8 +412,9 @@ public final class AlarmNotifications {
        notification.setFullScreenIntent(PendingIntent.getActivity(service,
                ALARM_FIRING_NOTIFICATION_ID, fullScreenIntent, PendingIntent.FLAG_UPDATE_CURRENT),
                true);
        notification.setPriority(NotificationCompat.PRIORITY_MAX);
        notification.setPriority(NotificationCompat.PRIORITY_HIGH);

        NotificationUtils.createChannel(service, FIRING_NOTIFICATION_CHANNEL_ID);
        clearNotification(service, instance);
        service.startForeground(ALARM_FIRING_NOTIFICATION_ID, notification.build());
    }
@@ -541,10 +434,10 @@ public final class AlarmNotifications {
    static void updateNotification(Context context, AlarmInstance instance) {
        switch (instance.mAlarmState) {
            case AlarmInstance.LOW_NOTIFICATION_STATE:
                showLowPriorityNotification(context, instance);
                showUpcomingNotification(context, instance, true);
                break;
            case AlarmInstance.HIGH_NOTIFICATION_STATE:
                showHighPriorityNotification(context, instance);
                showUpcomingNotification(context, instance, false);
                break;
            case AlarmInstance.SNOOZE_STATE:
                showSnoozeNotification(context, instance);
Loading