1 /*
2  * Copyright (C) 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.deskclock.alarms
18 
19 import android.annotation.TargetApi
20 import android.app.Notification
21 import android.app.NotificationChannel
22 import android.app.NotificationManager
23 import android.app.PendingIntent
24 import android.app.Service
25 import android.content.Context
26 import android.content.Intent
27 import android.content.res.Resources
28 import android.os.Build
29 import android.service.notification.StatusBarNotification
30 import androidx.core.app.NotificationCompat
31 import androidx.core.app.NotificationManagerCompat
32 import androidx.core.content.ContextCompat
33 
34 import com.android.deskclock.AlarmClockFragment
35 import com.android.deskclock.AlarmUtils
36 import com.android.deskclock.DeskClock
37 import com.android.deskclock.LogUtils
38 import com.android.deskclock.provider.Alarm
39 import com.android.deskclock.provider.AlarmInstance
40 import com.android.deskclock.provider.ClockContract.InstancesColumns
41 import com.android.deskclock.R
42 import com.android.deskclock.Utils
43 
44 import java.text.DateFormat
45 import java.text.SimpleDateFormat
46 import java.util.Locale
47 
48 internal object AlarmNotifications {
49     const val EXTRA_NOTIFICATION_ID = "extra_notification_id"
50 
51     /**
52      * Notification channel containing all low priority notifications.
53      */
54     private const val ALARM_LOW_PRIORITY_NOTIFICATION_CHANNEL_ID = "alarmLowPriorityNotification"
55 
56     /**
57      * Notification channel containing all high priority notifications.
58      */
59     private const val ALARM_HIGH_PRIORITY_NOTIFICATION_CHANNEL_ID = "alarmHighPriorityNotification"
60 
61     /**
62      * Notification channel containing all snooze notifications.
63      */
64     private const val ALARM_SNOOZE_NOTIFICATION_CHANNEL_ID = "alarmSnoozeNotification"
65 
66     /**
67      * Notification channel containing all missed notifications.
68      */
69     private const val ALARM_MISSED_NOTIFICATION_CHANNEL_ID = "alarmMissedNotification"
70 
71     /**
72      * Notification channel containing all alarm notifications.
73      */
74     private const val ALARM_NOTIFICATION_CHANNEL_ID = "alarmNotification"
75 
76     /**
77      * Formats times such that chronological order and lexicographical order agree.
78      */
79     private val SORT_KEY_FORMAT: DateFormat = SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", Locale.US)
80 
81     /**
82      * This value is coordinated with group ids from
83      * [com.android.deskclock.data.NotificationModel]
84      */
85     private const val UPCOMING_GROUP_KEY = "1"
86 
87     /**
88      * This value is coordinated with group ids from
89      * [com.android.deskclock.data.NotificationModel]
90      */
91     private const val MISSED_GROUP_KEY = "4"
92 
93     /**
94      * This value is coordinated with notification ids from
95      * [com.android.deskclock.data.NotificationModel]
96      */
97     private const val ALARM_GROUP_NOTIFICATION_ID = Int.MAX_VALUE - 4
98 
99     /**
100      * This value is coordinated with notification ids from
101      * [com.android.deskclock.data.NotificationModel]
102      */
103     private const val ALARM_GROUP_MISSED_NOTIFICATION_ID = Int.MAX_VALUE - 5
104 
105     /**
106      * This value is coordinated with notification ids from
107      * [com.android.deskclock.data.NotificationModel]
108      */
109     private const val ALARM_FIRING_NOTIFICATION_ID = Int.MAX_VALUE - 7
110 
111     @JvmStatic
112     @Synchronized
showLowPriorityNotificationnull113     fun showLowPriorityNotification(
114         context: Context,
115         instance: AlarmInstance
116     ) {
117         LogUtils.v("Displaying low priority notification for alarm instance: " + instance.mId)
118 
119         val builder: NotificationCompat.Builder = NotificationCompat.Builder(
120                 context, ALARM_LOW_PRIORITY_NOTIFICATION_CHANNEL_ID)
121                 .setShowWhen(false)
122                 .setContentTitle(context.getString(
123                         R.string.alarm_alert_predismiss_title))
124                 .setContentText(AlarmUtils.getAlarmText(
125                         context, instance, true /* includeLabel */))
126                 .setColor(ContextCompat.getColor(context, R.color.default_background))
127                 .setSmallIcon(R.drawable.stat_notify_alarm)
128                 .setAutoCancel(false)
129                 .setSortKey(createSortKey(instance))
130                 .setPriority(NotificationCompat.PRIORITY_DEFAULT)
131                 .setCategory(NotificationCompat.CATEGORY_EVENT)
132                 .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
133                 .setLocalOnly(true)
134 
135         if (Utils.isNOrLater()) {
136             builder.setGroup(UPCOMING_GROUP_KEY)
137         }
138 
139         // Setup up hide notification
140         val hideIntent: Intent = AlarmStateManager.createStateChangeIntent(context,
141                 AlarmStateManager.ALARM_DELETE_TAG, instance,
142                 InstancesColumns.HIDE_NOTIFICATION_STATE)
143         val id = instance.hashCode()
144         builder.setDeleteIntent(PendingIntent.getService(context, id,
145                 hideIntent, PendingIntent.FLAG_UPDATE_CURRENT))
146 
147         // Setup up dismiss action
148         val dismissIntent: Intent = AlarmStateManager.createStateChangeIntent(context,
149                 AlarmStateManager.ALARM_DISMISS_TAG, instance, InstancesColumns.PREDISMISSED_STATE)
150         builder.addAction(R.drawable.ic_alarm_off_24dp,
151                 context.getString(R.string.alarm_alert_dismiss_text),
152                 PendingIntent.getService(context, id,
153                         dismissIntent, PendingIntent.FLAG_UPDATE_CURRENT))
154 
155         // Setup content action if instance is owned by alarm
156         val viewAlarmIntent: Intent = createViewAlarmIntent(context, instance)
157         builder.setContentIntent(PendingIntent.getActivity(context, id,
158                 viewAlarmIntent, PendingIntent.FLAG_UPDATE_CURRENT))
159 
160         val nm: NotificationManagerCompat = NotificationManagerCompat.from(context)
161         if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
162             val channel = NotificationChannel(
163                     ALARM_LOW_PRIORITY_NOTIFICATION_CHANNEL_ID,
164                     context.getString(R.string.default_label),
165                     NotificationManagerCompat.IMPORTANCE_DEFAULT)
166             nm.createNotificationChannel(channel)
167         }
168         val notification: Notification = builder.build()
169         nm.notify(id, notification)
170         updateUpcomingAlarmGroupNotification(context, -1, notification)
171     }
172 
173     @JvmStatic
174     @Synchronized
showHighPriorityNotificationnull175     fun showHighPriorityNotification(
176         context: Context,
177         instance: AlarmInstance
178     ) {
179         LogUtils.v("Displaying high priority notification for alarm instance: " + instance.mId)
180 
181         val builder: NotificationCompat.Builder = NotificationCompat.Builder(
182                 context, ALARM_HIGH_PRIORITY_NOTIFICATION_CHANNEL_ID)
183                 .setShowWhen(false)
184                 .setContentTitle(context.getString(
185                         R.string.alarm_alert_predismiss_title))
186                 .setContentText(AlarmUtils.getAlarmText(
187                         context, instance, true /* includeLabel */))
188                 .setColor(ContextCompat.getColor(context, R.color.default_background))
189                 .setSmallIcon(R.drawable.stat_notify_alarm)
190                 .setAutoCancel(false)
191                 .setSortKey(createSortKey(instance))
192                 .setPriority(NotificationCompat.PRIORITY_HIGH)
193                 .setCategory(NotificationCompat.CATEGORY_EVENT)
194                 .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
195                 .setLocalOnly(true)
196 
197         if (Utils.isNOrLater()) {
198             builder.setGroup(UPCOMING_GROUP_KEY)
199         }
200 
201         // Setup up dismiss action
202         val dismissIntent: Intent = AlarmStateManager.createStateChangeIntent(context,
203                 AlarmStateManager.ALARM_DISMISS_TAG, instance, InstancesColumns.PREDISMISSED_STATE)
204         val id = instance.hashCode()
205         builder.addAction(R.drawable.ic_alarm_off_24dp,
206                 context.getString(R.string.alarm_alert_dismiss_text),
207                 PendingIntent.getService(context, id,
208                         dismissIntent, PendingIntent.FLAG_UPDATE_CURRENT))
209 
210         // Setup content action if instance is owned by alarm
211         val viewAlarmIntent: Intent = createViewAlarmIntent(context, instance)
212         builder.setContentIntent(PendingIntent.getActivity(context, id,
213                 viewAlarmIntent, PendingIntent.FLAG_UPDATE_CURRENT))
214 
215         val nm: NotificationManagerCompat = NotificationManagerCompat.from(context)
216         if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
217             val channel = NotificationChannel(
218                     ALARM_HIGH_PRIORITY_NOTIFICATION_CHANNEL_ID,
219                     context.getString(R.string.default_label),
220                     NotificationManagerCompat.IMPORTANCE_HIGH)
221             nm.createNotificationChannel(channel)
222         }
223         val notification: Notification = builder.build()
224         nm.notify(id, notification)
225         updateUpcomingAlarmGroupNotification(context, -1, notification)
226     }
227 
228     @TargetApi(Build.VERSION_CODES.N)
isGroupSummarynull229     private fun isGroupSummary(n: Notification): Boolean {
230         return n.flags and Notification.FLAG_GROUP_SUMMARY == Notification.FLAG_GROUP_SUMMARY
231     }
232 
233     /**
234      * Method which returns the first active notification for a given group. If a notification was
235      * just posted, provide it to make sure it is included as a potential result. If a notification
236      * was just canceled, provide the id so that it is not included as a potential result. These
237      * extra parameters are needed due to a race condition which exists in
238      * [NotificationManager.getActiveNotifications].
239      *
240      * @param context Context from which to grab the NotificationManager
241      * @param group The group key to query for notifications
242      * @param canceledNotificationId The id of the just-canceled notification (-1 if none)
243      * @param postedNotification The notification that was just posted
244      * @return The first active notification for the group
245      */
246     @TargetApi(Build.VERSION_CODES.N)
getFirstActiveNotificationnull247     private fun getFirstActiveNotification(
248         context: Context,
249         group: String,
250         canceledNotificationId: Int,
251         postedNotification: Notification?
252     ): Notification? {
253         val nm: NotificationManager =
254                 context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
255         val notifications: Array<StatusBarNotification> = nm.getActiveNotifications()
256         var firstActiveNotification: Notification? = postedNotification
257         for (statusBarNotification in notifications) {
258             val n: Notification = statusBarNotification.getNotification()
259             if (!isGroupSummary(n) && group == n.getGroup() &&
260                     statusBarNotification.getId() != canceledNotificationId) {
261                 if (firstActiveNotification == null ||
262                         n.getSortKey().compareTo(firstActiveNotification.getSortKey()) < 0) {
263                     firstActiveNotification = n
264                 }
265             }
266         }
267         return firstActiveNotification
268     }
269 
270     @TargetApi(Build.VERSION_CODES.N)
getActiveGroupSummaryNotificationnull271     private fun getActiveGroupSummaryNotification(context: Context, group: String): Notification? {
272         val nm: NotificationManager =
273                 context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
274         val notifications: Array<StatusBarNotification> = nm.getActiveNotifications()
275         for (statusBarNotification in notifications) {
276             val n: Notification = statusBarNotification.getNotification()
277             if (isGroupSummary(n) && group == n.getGroup()) {
278                 return n
279             }
280         }
281         return null
282     }
283 
updateUpcomingAlarmGroupNotificationnull284     private fun updateUpcomingAlarmGroupNotification(
285         context: Context,
286         canceledNotificationId: Int,
287         postedNotification: Notification?
288     ) {
289         if (!Utils.isNOrLater()) {
290             return
291         }
292 
293         val nm: NotificationManagerCompat = NotificationManagerCompat.from(context)
294         if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
295             val channel = NotificationChannel(
296                     ALARM_NOTIFICATION_CHANNEL_ID,
297                     context.getString(R.string.default_label),
298                     NotificationManagerCompat.IMPORTANCE_HIGH)
299             nm.createNotificationChannel(channel)
300         }
301 
302         val firstUpcoming: Notification? = getFirstActiveNotification(context, UPCOMING_GROUP_KEY,
303                 canceledNotificationId, postedNotification)
304         if (firstUpcoming == null) {
305             nm.cancel(ALARM_GROUP_NOTIFICATION_ID)
306             return
307         }
308 
309         var summary: Notification? = getActiveGroupSummaryNotification(context, UPCOMING_GROUP_KEY)
310         if (summary == null ||
311                 summary.contentIntent != firstUpcoming.contentIntent) {
312             summary = NotificationCompat.Builder(context, ALARM_NOTIFICATION_CHANNEL_ID)
313                     .setShowWhen(false)
314                     .setContentIntent(firstUpcoming.contentIntent)
315                     .setColor(ContextCompat.getColor(context, R.color.default_background))
316                     .setSmallIcon(R.drawable.stat_notify_alarm)
317                     .setGroup(UPCOMING_GROUP_KEY)
318                     .setGroupSummary(true)
319                     .setPriority(NotificationCompat.PRIORITY_HIGH)
320                     .setCategory(NotificationCompat.CATEGORY_EVENT)
321                     .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
322                     .setLocalOnly(true)
323                     .build()
324             nm.notify(ALARM_GROUP_NOTIFICATION_ID, summary)
325         }
326     }
327 
updateMissedAlarmGroupNotificationnull328     private fun updateMissedAlarmGroupNotification(
329         context: Context,
330         canceledNotificationId: Int,
331         postedNotification: Notification?
332     ) {
333         if (!Utils.isNOrLater()) {
334             return
335         }
336 
337         val nm: NotificationManagerCompat = NotificationManagerCompat.from(context)
338         if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
339             val channel = NotificationChannel(
340                     ALARM_NOTIFICATION_CHANNEL_ID,
341                     context.getString(R.string.default_label),
342                     NotificationManagerCompat.IMPORTANCE_HIGH)
343             nm.createNotificationChannel(channel)
344         }
345 
346         val firstMissed: Notification? = getFirstActiveNotification(context, MISSED_GROUP_KEY,
347                 canceledNotificationId, postedNotification)
348         if (firstMissed == null) {
349             nm.cancel(ALARM_GROUP_MISSED_NOTIFICATION_ID)
350             return
351         }
352 
353         var summary: Notification? = getActiveGroupSummaryNotification(context, MISSED_GROUP_KEY)
354         if (summary == null ||
355                 summary.contentIntent != firstMissed.contentIntent) {
356             if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
357                 val channel = NotificationChannel(
358                         ALARM_MISSED_NOTIFICATION_CHANNEL_ID,
359                         context.getString(R.string.default_label),
360                         NotificationManagerCompat.IMPORTANCE_HIGH)
361                 nm.createNotificationChannel(channel)
362             }
363             summary = NotificationCompat.Builder(context, ALARM_NOTIFICATION_CHANNEL_ID)
364                     .setShowWhen(false)
365                     .setContentIntent(firstMissed.contentIntent)
366                     .setColor(ContextCompat.getColor(context, R.color.default_background))
367                     .setSmallIcon(R.drawable.stat_notify_alarm)
368                     .setGroup(MISSED_GROUP_KEY)
369                     .setGroupSummary(true)
370                     .setPriority(NotificationCompat.PRIORITY_HIGH)
371                     .setCategory(NotificationCompat.CATEGORY_EVENT)
372                     .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
373                     .setLocalOnly(true)
374                     .build()
375             nm.notify(ALARM_GROUP_MISSED_NOTIFICATION_ID, summary)
376         }
377     }
378 
379     @JvmStatic
380     @Synchronized
showSnoozeNotificationnull381     fun showSnoozeNotification(
382         context: Context,
383         instance: AlarmInstance
384     ) {
385         LogUtils.v("Displaying snoozed notification for alarm instance: " + instance.mId)
386 
387         val builder: NotificationCompat.Builder = NotificationCompat.Builder(
388                 context, ALARM_SNOOZE_NOTIFICATION_CHANNEL_ID)
389                 .setShowWhen(false)
390                 .setContentTitle(instance.getLabelOrDefault(context))
391                 .setContentText(context.getString(R.string.alarm_alert_snooze_until,
392                         AlarmUtils.getFormattedTime(context, instance.alarmTime)))
393                 .setColor(ContextCompat.getColor(context, R.color.default_background))
394                 .setSmallIcon(R.drawable.stat_notify_alarm)
395                 .setAutoCancel(false)
396                 .setSortKey(createSortKey(instance))
397                 .setPriority(NotificationCompat.PRIORITY_MAX)
398                 .setCategory(NotificationCompat.CATEGORY_EVENT)
399                 .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
400                 .setLocalOnly(true)
401 
402         if (Utils.isNOrLater()) {
403             builder.setGroup(UPCOMING_GROUP_KEY)
404         }
405 
406         // Setup up dismiss action
407         val dismissIntent: Intent = AlarmStateManager.createStateChangeIntent(context,
408                 AlarmStateManager.ALARM_DISMISS_TAG, instance, InstancesColumns.DISMISSED_STATE)
409         val id = instance.hashCode()
410         builder.addAction(R.drawable.ic_alarm_off_24dp,
411                 context.getString(R.string.alarm_alert_dismiss_text),
412                 PendingIntent.getService(context, id,
413                         dismissIntent, PendingIntent.FLAG_UPDATE_CURRENT))
414 
415         // Setup content action if instance is owned by alarm
416         val viewAlarmIntent: Intent = createViewAlarmIntent(context, instance)
417         builder.setContentIntent(PendingIntent.getActivity(context, id,
418                 viewAlarmIntent, PendingIntent.FLAG_UPDATE_CURRENT))
419 
420         val nm: NotificationManagerCompat = NotificationManagerCompat.from(context)
421         if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
422             val channel = NotificationChannel(
423                     ALARM_SNOOZE_NOTIFICATION_CHANNEL_ID,
424                     context.getString(R.string.default_label),
425                     NotificationManagerCompat.IMPORTANCE_DEFAULT)
426             nm.createNotificationChannel(channel)
427         }
428         val notification: Notification = builder.build()
429         nm.notify(id, notification)
430         updateUpcomingAlarmGroupNotification(context, -1, notification)
431     }
432 
433     @JvmStatic
434     @Synchronized
showMissedNotificationnull435     fun showMissedNotification(
436         context: Context,
437         instance: AlarmInstance
438     ) {
439         LogUtils.v("Displaying missed notification for alarm instance: " + instance.mId)
440 
441         val label = instance.mLabel
442         val alarmTime: String = AlarmUtils.getFormattedTime(context, instance.alarmTime)
443         val builder: NotificationCompat.Builder = NotificationCompat.Builder(
444                 context, ALARM_MISSED_NOTIFICATION_CHANNEL_ID)
445                 .setShowWhen(false)
446                 .setContentTitle(context.getString(R.string.alarm_missed_title))
447                 .setContentText(if (instance.mLabel!!.isEmpty()) {
448                     alarmTime
449                 } else {
450                     context.getString(R.string.alarm_missed_text, alarmTime, label)
451                 })
452                 .setColor(ContextCompat.getColor(context, R.color.default_background))
453                 .setSortKey(createSortKey(instance))
454                 .setSmallIcon(R.drawable.stat_notify_alarm)
455                 .setPriority(NotificationCompat.PRIORITY_HIGH)
456                 .setCategory(NotificationCompat.CATEGORY_EVENT)
457                 .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
458                 .setLocalOnly(true)
459 
460         if (Utils.isNOrLater()) {
461             builder.setGroup(MISSED_GROUP_KEY)
462         }
463 
464         val id = instance.hashCode()
465 
466         // Setup dismiss intent
467         val dismissIntent: Intent = AlarmStateManager.createStateChangeIntent(context,
468                 AlarmStateManager.ALARM_DISMISS_TAG, instance, InstancesColumns.DISMISSED_STATE)
469         builder.setDeleteIntent(PendingIntent.getService(context, id,
470                 dismissIntent, PendingIntent.FLAG_UPDATE_CURRENT))
471 
472         // Setup content intent
473         val showAndDismiss: Intent = AlarmInstance.createIntent(context,
474                 AlarmStateManager::class.java, instance.mId)
475         showAndDismiss.putExtra(EXTRA_NOTIFICATION_ID, id)
476         showAndDismiss.setAction(AlarmStateManager.SHOW_AND_DISMISS_ALARM_ACTION)
477         builder.setContentIntent(PendingIntent.getBroadcast(context, id,
478                 showAndDismiss, PendingIntent.FLAG_UPDATE_CURRENT))
479 
480         val nm: NotificationManagerCompat = NotificationManagerCompat.from(context)
481         if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
482             val channel = NotificationChannel(
483                     ALARM_MISSED_NOTIFICATION_CHANNEL_ID,
484                     context.getString(R.string.default_label),
485                     NotificationManagerCompat.IMPORTANCE_DEFAULT)
486             nm.createNotificationChannel(channel)
487         }
488         val notification: Notification = builder.build()
489         nm.notify(id, notification)
490         updateMissedAlarmGroupNotification(context, -1, notification)
491     }
492 
493     @Synchronized
showAlarmNotificationnull494     fun showAlarmNotification(service: Service, instance: AlarmInstance) {
495         LogUtils.v("Displaying alarm notification for alarm instance: " + instance.mId)
496 
497         val resources: Resources = service.getResources()
498         val notification: NotificationCompat.Builder = NotificationCompat.Builder(
499                 service, ALARM_NOTIFICATION_CHANNEL_ID)
500                 .setContentTitle(instance.getLabelOrDefault(service))
501                 .setContentText(AlarmUtils.getFormattedTime(
502                         service, instance.alarmTime))
503                 .setColor(ContextCompat.getColor(service, R.color.default_background))
504                 .setSmallIcon(R.drawable.stat_notify_alarm)
505                 .setOngoing(true)
506                 .setAutoCancel(false)
507                 .setDefaults(NotificationCompat.DEFAULT_LIGHTS)
508                 .setWhen(0)
509                 .setCategory(NotificationCompat.CATEGORY_ALARM)
510                 .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
511                 .setLocalOnly(true)
512 
513         // Setup Snooze Action
514         val snoozeIntent: Intent = AlarmStateManager.createStateChangeIntent(service,
515                 AlarmStateManager.ALARM_SNOOZE_TAG, instance, InstancesColumns.SNOOZE_STATE)
516         snoozeIntent.putExtra(AlarmStateManager.FROM_NOTIFICATION_EXTRA, true)
517         val snoozePendingIntent: PendingIntent = PendingIntent.getService(service,
518                 ALARM_FIRING_NOTIFICATION_ID, snoozeIntent, PendingIntent.FLAG_UPDATE_CURRENT)
519         notification.addAction(R.drawable.ic_snooze_24dp,
520                 resources.getString(R.string.alarm_alert_snooze_text), snoozePendingIntent)
521 
522         // Setup Dismiss Action
523         val dismissIntent: Intent = AlarmStateManager.createStateChangeIntent(service,
524                 AlarmStateManager.ALARM_DISMISS_TAG, instance, InstancesColumns.DISMISSED_STATE)
525         dismissIntent.putExtra(AlarmStateManager.FROM_NOTIFICATION_EXTRA, true)
526         val dismissPendingIntent: PendingIntent = PendingIntent.getService(service,
527                 ALARM_FIRING_NOTIFICATION_ID, dismissIntent, PendingIntent.FLAG_UPDATE_CURRENT)
528         notification.addAction(R.drawable.ic_alarm_off_24dp,
529                 resources.getString(R.string.alarm_alert_dismiss_text),
530                 dismissPendingIntent)
531 
532         // Setup Content Action
533         val contentIntent: Intent = AlarmInstance.createIntent(service, AlarmActivity::class.java,
534                 instance.mId)
535         notification.setContentIntent(PendingIntent.getActivity(service,
536                 ALARM_FIRING_NOTIFICATION_ID, contentIntent, PendingIntent.FLAG_UPDATE_CURRENT))
537 
538         // Setup fullscreen intent
539         val fullScreenIntent: Intent =
540                 AlarmInstance.createIntent(service, AlarmActivity::class.java, instance.mId)
541         // set action, so we can be different then content pending intent
542         fullScreenIntent.setAction("fullscreen_activity")
543         fullScreenIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK or
544                 Intent.FLAG_ACTIVITY_NO_USER_ACTION)
545         notification.setFullScreenIntent(PendingIntent.getActivity(service,
546                 ALARM_FIRING_NOTIFICATION_ID, fullScreenIntent, PendingIntent.FLAG_UPDATE_CURRENT),
547                 true)
548         notification.setPriority(NotificationCompat.PRIORITY_MAX)
549 
550         clearNotification(service, instance)
551         service.startForeground(ALARM_FIRING_NOTIFICATION_ID, notification.build())
552     }
553 
554     @JvmStatic
555     @Synchronized
clearNotificationnull556     fun clearNotification(context: Context, instance: AlarmInstance) {
557         LogUtils.v("Clearing notifications for alarm instance: " + instance.mId)
558         val nm: NotificationManagerCompat = NotificationManagerCompat.from(context)
559         val id = instance.hashCode()
560         nm.cancel(id)
561         updateUpcomingAlarmGroupNotification(context, id, null)
562         updateMissedAlarmGroupNotification(context, id, null)
563     }
564 
565     /**
566      * Updates the notification for an existing alarm. Use if the label has changed.
567      */
568     @JvmStatic
updateNotificationnull569     fun updateNotification(context: Context, instance: AlarmInstance) {
570         when (instance.mAlarmState) {
571             InstancesColumns.LOW_NOTIFICATION_STATE -> {
572                 showLowPriorityNotification(context, instance)
573             }
574             InstancesColumns.HIGH_NOTIFICATION_STATE -> {
575                 showHighPriorityNotification(context, instance)
576             }
577             InstancesColumns.SNOOZE_STATE -> showSnoozeNotification(context, instance)
578             InstancesColumns.MISSED_STATE -> showMissedNotification(context, instance)
579             else -> LogUtils.d("No notification to update")
580         }
581     }
582 
583     @JvmStatic
createViewAlarmIntentnull584     fun createViewAlarmIntent(context: Context?, instance: AlarmInstance): Intent {
585         val alarmId = instance.mAlarmId ?: Alarm.INVALID_ID
586         return Alarm.createIntent(context, DeskClock::class.java, alarmId)
587                 .putExtra(AlarmClockFragment.SCROLL_TO_ALARM_INTENT_EXTRA, alarmId)
588                 .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
589     }
590 
591     /**
592      * Alarm notifications are sorted chronologically. Missed alarms are sorted chronologically
593      * **after** all upcoming/snoozed alarms by including the "MISSED" prefix on the
594      * sort key.
595      *
596      * @param instance the alarm instance for which the notification is generated
597      * @return the sort key that specifies the order of this alarm notification
598      */
createSortKeynull599     private fun createSortKey(instance: AlarmInstance): String {
600         val timeKey = SORT_KEY_FORMAT.format(instance.alarmTime.time)
601         val missedAlarm = instance.mAlarmState == InstancesColumns.MISSED_STATE
602         return if (missedAlarm) "MISSED $timeKey" else timeKey
603     }
604 }