1 /* 2 * Copyright (C) 2007 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; 18 19 import android.app.AlarmManager; 20 import android.app.NotificationManager; 21 import android.app.PendingIntent; 22 import android.content.ContentResolver; 23 import android.content.ContentUris; 24 import android.content.ContentValues; 25 import android.content.Context; 26 import android.content.CursorLoader; 27 import android.content.Intent; 28 import android.content.SharedPreferences; 29 import android.database.Cursor; 30 import android.net.Uri; 31 import android.os.Parcel; 32 import android.provider.Settings; 33 import android.text.TextUtils; 34 import android.text.format.DateFormat; 35 36 import java.util.Calendar; 37 import java.util.HashSet; 38 import java.util.Set; 39 40 /** 41 * The Alarms provider supplies info about Alarm Clock settings 42 */ 43 public class Alarms { 44 45 static final String PREFERENCES = "AlarmClock"; 46 47 // This action triggers the AlarmReceiver as well as the AlarmKlaxon. It 48 // is a public action used in the manifest for receiving Alarm broadcasts 49 // from the alarm manager. 50 public static final String ALARM_ALERT_ACTION = "com.android.deskclock.ALARM_ALERT"; 51 52 // A public action sent by AlarmKlaxon when the alarm has stopped sounding 53 // for any reason (e.g. because it has been dismissed from AlarmAlertFullScreen, 54 // or killed due to an incoming phone call, etc). 55 public static final String ALARM_DONE_ACTION = "com.android.deskclock.ALARM_DONE"; 56 57 // AlarmAlertFullScreen listens for this broadcast intent, so that other applications 58 // can snooze the alarm (after ALARM_ALERT_ACTION and before ALARM_DONE_ACTION). 59 public static final String ALARM_SNOOZE_ACTION = "com.android.deskclock.ALARM_SNOOZE"; 60 61 // AlarmAlertFullScreen listens for this broadcast intent, so that other applications 62 // can dismiss the alarm (after ALARM_ALERT_ACTION and before ALARM_DONE_ACTION). 63 public static final String ALARM_DISMISS_ACTION = "com.android.deskclock.ALARM_DISMISS"; 64 65 // A public action sent by AlarmAlertFullScreen when a snoozed alarm was dismissed due 66 // to it handling ALARM_DISMISS_ACTION cancelled 67 public static final String ALARM_SNOOZE_CANCELLED = "com.android.deskclock.ALARM_SNOOZE_CANCELLED"; 68 69 // A broadcast sent every time the next alarm time is set in the system 70 public static final String NEXT_ALARM_TIME_SET = "com.android.deskclock.NEXT_ALARM_TIME_SET"; 71 72 // This is a private action used by the AlarmKlaxon to update the UI to 73 // show the alarm has been killed. 74 public static final String ALARM_KILLED = "alarm_killed"; 75 76 // Extra in the ALARM_KILLED intent to indicate to the user how long the 77 // alarm played before being killed. 78 public static final String ALARM_KILLED_TIMEOUT = "alarm_killed_timeout"; 79 80 // Extra in the ALARM_KILLED intent to indicate when alarm was replaced 81 public static final String ALARM_REPLACED = "alarm_replaced"; 82 83 // This string is used to indicate a silent alarm in the db. 84 public static final String ALARM_ALERT_SILENT = "silent"; 85 86 // This intent is sent from the notification when the user cancels the 87 // snooze alert. 88 public static final String CANCEL_SNOOZE = "cancel_snooze"; 89 90 // This string is used when passing an Alarm object through an intent. 91 public static final String ALARM_INTENT_EXTRA = "intent.extra.alarm"; 92 93 // This extra is the raw Alarm object data. It is used in the 94 // AlarmManagerService to avoid a ClassNotFoundException when filling in 95 // the Intent extras. 96 public static final String ALARM_RAW_DATA = "intent.extra.alarm_raw"; 97 98 private static final String PREF_SNOOZE_IDS = "snooze_ids"; 99 private static final String PREF_SNOOZE_TIME = "snooze_time"; 100 101 private final static String DM12 = "E h:mm aa"; 102 private final static String DM24 = "E kk:mm"; 103 104 private final static String M12 = "h:mm aa"; 105 // Shared with DigitalClock 106 final static String M24 = "kk:mm"; 107 108 final static int INVALID_ALARM_ID = -1; 109 110 /** 111 * Creates a new Alarm and fills in the given alarm's id. 112 */ addAlarm(Context context, Alarm alarm)113 public static long addAlarm(Context context, Alarm alarm) { 114 ContentValues values = createContentValues(alarm); 115 Uri uri = context.getContentResolver().insert( 116 Alarm.Columns.CONTENT_URI, values); 117 alarm.id = (int) ContentUris.parseId(uri); 118 119 long timeInMillis = calculateAlarm(alarm); 120 if (alarm.enabled) { 121 clearSnoozeIfNeeded(context, timeInMillis); 122 } 123 setNextAlert(context); 124 return timeInMillis; 125 } 126 127 /** 128 * Removes an existing Alarm. If this alarm is snoozing, disables 129 * snooze. Sets next alert. 130 */ deleteAlarm(Context context, int alarmId)131 public static void deleteAlarm(Context context, int alarmId) { 132 if (alarmId == INVALID_ALARM_ID) return; 133 134 ContentResolver contentResolver = context.getContentResolver(); 135 /* If alarm is snoozing, lose it */ 136 disableSnoozeAlert(context, alarmId); 137 138 Uri uri = ContentUris.withAppendedId(Alarm.Columns.CONTENT_URI, alarmId); 139 contentResolver.delete(uri, "", null); 140 141 setNextAlert(context); 142 } 143 144 getAlarmsCursorLoader(Context context)145 public static CursorLoader getAlarmsCursorLoader(Context context) { 146 return new CursorLoader(context, Alarm.Columns.CONTENT_URI, 147 Alarm.Columns.ALARM_QUERY_COLUMNS, null, null, Alarm.Columns.DEFAULT_SORT_ORDER); 148 } 149 150 /** 151 * Queries all alarms 152 * @return cursor over all alarms 153 */ getAlarmsCursor(ContentResolver contentResolver)154 public static Cursor getAlarmsCursor(ContentResolver contentResolver) { 155 return contentResolver.query( 156 Alarm.Columns.CONTENT_URI, Alarm.Columns.ALARM_QUERY_COLUMNS, 157 null, null, Alarm.Columns.DEFAULT_SORT_ORDER); 158 } 159 160 // Private method to get a more limited set of alarms from the database. getFilteredAlarmsCursor( ContentResolver contentResolver)161 private static Cursor getFilteredAlarmsCursor( 162 ContentResolver contentResolver) { 163 return contentResolver.query(Alarm.Columns.CONTENT_URI, 164 Alarm.Columns.ALARM_QUERY_COLUMNS, Alarm.Columns.WHERE_ENABLED, 165 null, null); 166 } 167 createContentValues(Alarm alarm)168 private static ContentValues createContentValues(Alarm alarm) { 169 ContentValues values = new ContentValues(8); 170 // Set the alarm_time value if this alarm does not repeat. This will be 171 // used later to disable expire alarms. 172 long time = 0; 173 if (!alarm.daysOfWeek.isRepeatSet()) { 174 time = calculateAlarm(alarm); 175 } 176 177 // -1 means generate new id. 178 if (alarm.id != -1) { 179 values.put(Alarm.Columns._ID, alarm.id); 180 } 181 182 values.put(Alarm.Columns.ENABLED, alarm.enabled ? 1 : 0); 183 values.put(Alarm.Columns.HOUR, alarm.hour); 184 values.put(Alarm.Columns.MINUTES, alarm.minutes); 185 values.put(Alarm.Columns.ALARM_TIME, time); 186 values.put(Alarm.Columns.DAYS_OF_WEEK, alarm.daysOfWeek.getCoded()); 187 values.put(Alarm.Columns.VIBRATE, alarm.vibrate); 188 values.put(Alarm.Columns.MESSAGE, alarm.label); 189 190 // A null alert Uri indicates a silent alarm. 191 values.put(Alarm.Columns.ALERT, alarm.alert == null ? ALARM_ALERT_SILENT 192 : alarm.alert.toString()); 193 194 return values; 195 } 196 clearSnoozeIfNeeded(Context context, long alarmTime)197 private static void clearSnoozeIfNeeded(Context context, long alarmTime) { 198 // If this alarm fires before the next snooze, clear the snooze to 199 // enable this alarm. 200 SharedPreferences prefs = context.getSharedPreferences(PREFERENCES, 0); 201 202 // Get the list of snoozed alarms 203 final Set<String> snoozedIds = prefs.getStringSet(PREF_SNOOZE_IDS, new HashSet<String>()); 204 for (String snoozedAlarm : snoozedIds) { 205 final long snoozeTime = prefs.getLong(getAlarmPrefSnoozeTimeKey(snoozedAlarm), 0); 206 if (alarmTime < snoozeTime) { 207 final int alarmId = Integer.parseInt(snoozedAlarm); 208 clearSnoozePreference(context, prefs, alarmId); 209 } 210 } 211 } 212 213 /** 214 * Return an Alarm object representing the alarm id in the database. 215 * Returns null if no alarm exists. 216 */ getAlarm(ContentResolver contentResolver, int alarmId)217 public static Alarm getAlarm(ContentResolver contentResolver, int alarmId) { 218 Cursor cursor = contentResolver.query( 219 ContentUris.withAppendedId(Alarm.Columns.CONTENT_URI, alarmId), 220 Alarm.Columns.ALARM_QUERY_COLUMNS, 221 null, null, null); 222 Alarm alarm = null; 223 if (cursor != null) { 224 if (cursor.moveToFirst()) { 225 alarm = new Alarm(cursor); 226 } 227 cursor.close(); 228 } 229 return alarm; 230 } 231 232 233 /** 234 * A convenience method to set an alarm in the Alarms 235 * content provider. 236 * @return Time when the alarm will fire. Or < 1 if update failed. 237 */ setAlarm(Context context, Alarm alarm)238 public static long setAlarm(Context context, Alarm alarm) { 239 ContentValues values = createContentValues(alarm); 240 ContentResolver resolver = context.getContentResolver(); 241 long rowsUpdated = resolver.update( 242 ContentUris.withAppendedId(Alarm.Columns.CONTENT_URI, alarm.id), 243 values, null, null); 244 if (rowsUpdated < 1) { 245 Log.e("Error updating alarm " + alarm); 246 return rowsUpdated; 247 } 248 249 long timeInMillis = calculateAlarm(alarm); 250 251 if (alarm.enabled) { 252 // Disable the snooze if we just changed the snoozed alarm. This 253 // only does work if the snoozed alarm is the same as the given 254 // alarm. 255 // TODO: disableSnoozeAlert should have a better name. 256 disableSnoozeAlert(context, alarm.id); 257 258 // Disable the snooze if this alarm fires before the snoozed alarm. 259 // This works on every alarm since the user most likely intends to 260 // have the modified alarm fire next. 261 clearSnoozeIfNeeded(context, timeInMillis); 262 } 263 264 setNextAlert(context); 265 266 return timeInMillis; 267 } 268 269 /** 270 * A convenience method to enable or disable an alarm. 271 * 272 * @param id corresponds to the _id column 273 * @param enabled corresponds to the ENABLED column 274 */ 275 enableAlarm( final Context context, final int id, boolean enabled)276 public static void enableAlarm( 277 final Context context, final int id, boolean enabled) { 278 enableAlarmInternal(context, id, enabled); 279 setNextAlert(context); 280 } 281 enableAlarmInternal(final Context context, final int id, boolean enabled)282 private static void enableAlarmInternal(final Context context, 283 final int id, boolean enabled) { 284 enableAlarmInternal(context, getAlarm(context.getContentResolver(), id), 285 enabled); 286 } 287 enableAlarmInternal(final Context context, final Alarm alarm, boolean enabled)288 private static void enableAlarmInternal(final Context context, 289 final Alarm alarm, boolean enabled) { 290 if (alarm == null) { 291 return; 292 } 293 ContentResolver resolver = context.getContentResolver(); 294 295 ContentValues values = new ContentValues(2); 296 values.put(Alarm.Columns.ENABLED, enabled ? 1 : 0); 297 298 // If we are enabling the alarm, calculate alarm time since the time 299 // value in Alarm may be old. 300 if (enabled) { 301 long time = 0; 302 if (!alarm.daysOfWeek.isRepeatSet()) { 303 time = calculateAlarm(alarm); 304 } 305 values.put(Alarm.Columns.ALARM_TIME, time); 306 } else { 307 // Clear the snooze if the id matches. 308 disableSnoozeAlert(context, alarm.id); 309 } 310 311 resolver.update(ContentUris.withAppendedId( 312 Alarm.Columns.CONTENT_URI, alarm.id), values, null, null); 313 } 314 calculateNextAlert(final Context context)315 private static Alarm calculateNextAlert(final Context context) { 316 long minTime = Long.MAX_VALUE; 317 long now = System.currentTimeMillis(); 318 final SharedPreferences prefs = context.getSharedPreferences(PREFERENCES, 0); 319 320 Set<Alarm> alarms = new HashSet<Alarm>(); 321 322 // We need to to build the list of alarms from both the snoozed list and the scheduled 323 // list. For a non-repeating alarm, when it goes of, it becomes disabled. A snoozed 324 // non-repeating alarm is not in the active list in the database. 325 326 // first go through the snoozed alarms 327 final Set<String> snoozedIds = prefs.getStringSet(PREF_SNOOZE_IDS, new HashSet<String>()); 328 for (String snoozedAlarm : snoozedIds) { 329 final int alarmId = Integer.parseInt(snoozedAlarm); 330 final Alarm a = getAlarm(context.getContentResolver(), alarmId); 331 alarms.add(a); 332 } 333 334 // Now add the scheduled alarms 335 final Cursor cursor = getFilteredAlarmsCursor(context.getContentResolver()); 336 if (cursor != null) { 337 try { 338 if (cursor.moveToFirst()) { 339 do { 340 final Alarm a = new Alarm(cursor); 341 alarms.add(a); 342 } while (cursor.moveToNext()); 343 } 344 } finally { 345 cursor.close(); 346 } 347 } 348 349 Alarm alarm = null; 350 351 for (Alarm a : alarms) { 352 // A time of 0 indicates this is a repeating alarm, so 353 // calculate the time to get the next alert. 354 if (a.time == 0) { 355 a.time = calculateAlarm(a); 356 } 357 358 // Update the alarm if it has been snoozed 359 updateAlarmTimeForSnooze(prefs, a); 360 361 if (a.time < now) { 362 Log.v("Disabling expired alarm set for " + Log.formatTime(a.time)); 363 // Expired alarm, disable it and move along. 364 enableAlarmInternal(context, a, false); 365 continue; 366 } 367 if (a.time < minTime) { 368 minTime = a.time; 369 alarm = a; 370 } 371 } 372 373 return alarm; 374 } 375 376 /** 377 * Disables non-repeating alarms that have passed. Called at 378 * boot. 379 */ disableExpiredAlarms(final Context context)380 public static void disableExpiredAlarms(final Context context) { 381 Cursor cur = getFilteredAlarmsCursor(context.getContentResolver()); 382 long now = System.currentTimeMillis(); 383 384 try { 385 if (cur.moveToFirst()) { 386 do { 387 Alarm alarm = new Alarm(cur); 388 // A time of 0 means this alarm repeats. If the time is 389 // non-zero, check if the time is before now. 390 if (alarm.time != 0 && alarm.time < now) { 391 Log.v("Disabling expired alarm set for " + 392 Log.formatTime(alarm.time)); 393 enableAlarmInternal(context, alarm, false); 394 } 395 } while (cur.moveToNext()); 396 } 397 } finally { 398 cur.close(); 399 } 400 } 401 402 /** 403 * Called at system startup, on time/timezone change, and whenever 404 * the user changes alarm settings. Activates snooze if set, 405 * otherwise loads all alarms, activates next alert. 406 */ setNextAlert(final Context context)407 public static void setNextAlert(final Context context) { 408 final Alarm alarm = calculateNextAlert(context); 409 if (alarm != null) { 410 enableAlert(context, alarm, alarm.time); 411 } else { 412 disableAlert(context); 413 } 414 Intent i = new Intent(NEXT_ALARM_TIME_SET); 415 context.sendBroadcast(i); 416 } 417 418 /** 419 * Sets alert in AlarmManger and StatusBar. This is what will 420 * actually launch the alert when the alarm triggers. 421 * 422 * @param alarm Alarm. 423 * @param atTimeInMillis milliseconds since epoch 424 */ enableAlert(Context context, final Alarm alarm, final long atTimeInMillis)425 private static void enableAlert(Context context, final Alarm alarm, 426 final long atTimeInMillis) { 427 AlarmManager am = (AlarmManager) 428 context.getSystemService(Context.ALARM_SERVICE); 429 430 // Intentionally verbose: always log the alarm time to provide useful 431 // information in bug reports. 432 Log.v("Alarm set for id=" + alarm.id + " " + Log.formatTime(atTimeInMillis)); 433 434 Intent intent = new Intent(ALARM_ALERT_ACTION); 435 436 // XXX: This is a slight hack to avoid an exception in the remote 437 // AlarmManagerService process. The AlarmManager adds extra data to 438 // this Intent which causes it to inflate. Since the remote process 439 // does not know about the Alarm class, it throws a 440 // ClassNotFoundException. 441 // 442 // To avoid this, we marshall the data ourselves and then parcel a plain 443 // byte[] array. The AlarmReceiver class knows to build the Alarm 444 // object from the byte[] array. 445 Parcel out = Parcel.obtain(); 446 alarm.writeToParcel(out, 0); 447 out.setDataPosition(0); 448 intent.putExtra(ALARM_RAW_DATA, out.marshall()); 449 450 PendingIntent sender = PendingIntent.getBroadcast( 451 context, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT); 452 453 am.set(AlarmManager.RTC_WAKEUP, atTimeInMillis, sender); 454 455 setStatusBarIcon(context, true); 456 457 Calendar c = Calendar.getInstance(); 458 c.setTimeInMillis(atTimeInMillis); 459 String timeString = formatDayAndTime(context, c); 460 saveNextAlarm(context, timeString); 461 } 462 463 /** 464 * Disables alert in AlarmManager and StatusBar. 465 * 466 * @param context The context 467 */ disableAlert(Context context)468 static void disableAlert(Context context) { 469 AlarmManager am = (AlarmManager) 470 context.getSystemService(Context.ALARM_SERVICE); 471 PendingIntent sender = PendingIntent.getBroadcast( 472 context, 0, new Intent(ALARM_ALERT_ACTION), 473 PendingIntent.FLAG_CANCEL_CURRENT); 474 am.cancel(sender); 475 setStatusBarIcon(context, false); 476 // Intentionally verbose: always log the lack of a next alarm to provide useful 477 // information in bug reports. 478 Log.v("No next alarm"); 479 saveNextAlarm(context, ""); 480 } 481 saveSnoozeAlert(final Context context, final int id, final long time)482 static void saveSnoozeAlert(final Context context, final int id, 483 final long time) { 484 SharedPreferences prefs = context.getSharedPreferences(PREFERENCES, 0); 485 if (id == INVALID_ALARM_ID) { 486 clearAllSnoozePreferences(context, prefs); 487 } else { 488 final Set<String> snoozedIds = 489 prefs.getStringSet(PREF_SNOOZE_IDS, new HashSet<String>()); 490 snoozedIds.add(Integer.toString(id)); 491 final SharedPreferences.Editor ed = prefs.edit(); 492 ed.putStringSet(PREF_SNOOZE_IDS, snoozedIds); 493 ed.putLong(getAlarmPrefSnoozeTimeKey(id), time); 494 ed.apply(); 495 } 496 // Set the next alert after updating the snooze. 497 setNextAlert(context); 498 } 499 getAlarmPrefSnoozeTimeKey(int id)500 private static String getAlarmPrefSnoozeTimeKey(int id) { 501 return getAlarmPrefSnoozeTimeKey(Integer.toString(id)); 502 } 503 getAlarmPrefSnoozeTimeKey(String id)504 private static String getAlarmPrefSnoozeTimeKey(String id) { 505 return PREF_SNOOZE_TIME + id; 506 } 507 508 /** 509 * Disable the snooze alert if the given id matches the snooze id. 510 */ disableSnoozeAlert(final Context context, final int id)511 static void disableSnoozeAlert(final Context context, final int id) { 512 SharedPreferences prefs = context.getSharedPreferences(PREFERENCES, 0); 513 if (hasAlarmBeenSnoozed(prefs, id)) { 514 // This is the same id so clear the shared prefs. 515 clearSnoozePreference(context, prefs, id); 516 } 517 } 518 519 // Helper to remove the snooze preference. Do not use clear because that 520 // will erase the clock preferences. Also clear the snooze notification in 521 // the window shade. clearSnoozePreference(final Context context, final SharedPreferences prefs, final int id)522 private static void clearSnoozePreference(final Context context, 523 final SharedPreferences prefs, final int id) { 524 final String alarmStr = Integer.toString(id); 525 final Set<String> snoozedIds = 526 prefs.getStringSet(PREF_SNOOZE_IDS, new HashSet<String>()); 527 if (snoozedIds.contains(alarmStr)) { 528 NotificationManager nm = (NotificationManager) 529 context.getSystemService(Context.NOTIFICATION_SERVICE); 530 nm.cancel(id); 531 } 532 533 final SharedPreferences.Editor ed = prefs.edit(); 534 snoozedIds.remove(alarmStr); 535 ed.putStringSet(PREF_SNOOZE_IDS, snoozedIds); 536 ed.remove(getAlarmPrefSnoozeTimeKey(alarmStr)); 537 ed.apply(); 538 } 539 clearAllSnoozePreferences(final Context context, final SharedPreferences prefs)540 private static void clearAllSnoozePreferences(final Context context, 541 final SharedPreferences prefs) { 542 NotificationManager nm = (NotificationManager) 543 context.getSystemService(Context.NOTIFICATION_SERVICE); 544 final Set<String> snoozedIds = 545 prefs.getStringSet(PREF_SNOOZE_IDS, new HashSet<String>()); 546 final SharedPreferences.Editor ed = prefs.edit(); 547 for (String snoozeId : snoozedIds) { 548 nm.cancel(Integer.parseInt(snoozeId)); 549 ed.remove(getAlarmPrefSnoozeTimeKey(snoozeId)); 550 } 551 552 ed.remove(PREF_SNOOZE_IDS); 553 ed.apply(); 554 } 555 hasAlarmBeenSnoozed(final SharedPreferences prefs, final int alarmId)556 private static boolean hasAlarmBeenSnoozed(final SharedPreferences prefs, final int alarmId) { 557 final Set<String> snoozedIds = prefs.getStringSet(PREF_SNOOZE_IDS, null); 558 559 // Return true if there a valid snoozed alarmId was saved 560 return snoozedIds != null && snoozedIds.contains(Integer.toString(alarmId)); 561 } 562 563 /** 564 * Updates the specified Alarm with the additional snooze time. 565 * Returns a boolean indicating whether the alarm was updated. 566 */ updateAlarmTimeForSnooze( final SharedPreferences prefs, final Alarm alarm)567 private static boolean updateAlarmTimeForSnooze( 568 final SharedPreferences prefs, final Alarm alarm) { 569 if (!hasAlarmBeenSnoozed(prefs, alarm.id)) { 570 // No need to modify the alarm 571 return false; 572 } 573 574 final long time = prefs.getLong(getAlarmPrefSnoozeTimeKey(alarm.id), -1); 575 // The time in the database is either 0 (repeating) or a specific time 576 // for a non-repeating alarm. Update this value so the AlarmReceiver 577 // has the right time to compare. 578 alarm.time = time; 579 580 return true; 581 } 582 583 /** 584 * Tells the StatusBar whether the alarm is enabled or disabled 585 */ setStatusBarIcon(Context context, boolean enabled)586 private static void setStatusBarIcon(Context context, boolean enabled) { 587 Intent alarmChanged = new Intent("android.intent.action.ALARM_CHANGED"); 588 alarmChanged.putExtra("alarmSet", enabled); 589 context.sendBroadcast(alarmChanged); 590 } 591 calculateAlarm(Alarm alarm)592 private static long calculateAlarm(Alarm alarm) { 593 return calculateAlarm(alarm.hour, alarm.minutes, alarm.daysOfWeek) 594 .getTimeInMillis(); 595 } 596 597 /** 598 * Given an alarm in hours and minutes, return a time suitable for 599 * setting in AlarmManager. 600 */ calculateAlarm(int hour, int minute, Alarm.DaysOfWeek daysOfWeek)601 static Calendar calculateAlarm(int hour, int minute, 602 Alarm.DaysOfWeek daysOfWeek) { 603 604 // start with now 605 Calendar c = Calendar.getInstance(); 606 c.setTimeInMillis(System.currentTimeMillis()); 607 608 int nowHour = c.get(Calendar.HOUR_OF_DAY); 609 int nowMinute = c.get(Calendar.MINUTE); 610 611 // if alarm is behind current time, advance one day 612 if (hour < nowHour || 613 hour == nowHour && minute <= nowMinute) { 614 c.add(Calendar.DAY_OF_YEAR, 1); 615 } 616 c.set(Calendar.HOUR_OF_DAY, hour); 617 c.set(Calendar.MINUTE, minute); 618 c.set(Calendar.SECOND, 0); 619 c.set(Calendar.MILLISECOND, 0); 620 621 int addDays = daysOfWeek.getNextAlarm(c); 622 if (addDays > 0) c.add(Calendar.DAY_OF_WEEK, addDays); 623 return c; 624 } 625 formatTime(final Context context, int hour, int minute, Alarm.DaysOfWeek daysOfWeek)626 static String formatTime(final Context context, int hour, int minute, 627 Alarm.DaysOfWeek daysOfWeek) { 628 Calendar c = calculateAlarm(hour, minute, daysOfWeek); 629 return formatTime(context, c); 630 } 631 632 /* used by AlarmAlert */ formatTime(final Context context, Calendar c)633 static String formatTime(final Context context, Calendar c) { 634 String format = get24HourMode(context) ? M24 : M12; 635 return (c == null) ? "" : (String)DateFormat.format(format, c); 636 } 637 638 /** 639 * Shows day and time -- used for lock screen 640 */ formatDayAndTime(final Context context, Calendar c)641 private static String formatDayAndTime(final Context context, Calendar c) { 642 String format = get24HourMode(context) ? DM24 : DM12; 643 return (c == null) ? "" : (String)DateFormat.format(format, c); 644 } 645 646 /** 647 * Save time of the next alarm, as a formatted string, into the system 648 * settings so those who care can make use of it. 649 */ saveNextAlarm(final Context context, String timeString)650 static void saveNextAlarm(final Context context, String timeString) { 651 Log.v("Setting next alarm string in system to " + 652 (TextUtils.isEmpty(timeString) ? "null" : timeString)); 653 Settings.System.putString(context.getContentResolver(), 654 Settings.System.NEXT_ALARM_FORMATTED, 655 timeString); 656 } 657 658 /** 659 * @return true if clock is set to 24-hour mode 660 */ get24HourMode(final Context context)661 public static boolean get24HourMode(final Context context) { 662 return android.text.format.DateFormat.is24HourFormat(context); 663 } 664 } 665