• Home
  • History
  • Annotate
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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  }