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