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