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 }