1 /* 2 * Copyright (C) 2010 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.Activity; 20 import android.content.ContentResolver; 21 import android.content.ContentUris; 22 import android.content.Context; 23 import android.content.Intent; 24 import android.net.Uri; 25 import android.os.AsyncTask; 26 import android.os.Bundle; 27 import android.os.Parcelable; 28 import android.provider.AlarmClock; 29 import android.text.TextUtils; 30 import android.text.format.DateFormat; 31 32 import com.android.deskclock.alarms.AlarmStateManager; 33 import com.android.deskclock.controller.Controller; 34 import com.android.deskclock.data.DataModel; 35 import com.android.deskclock.data.Timer; 36 import com.android.deskclock.data.Weekdays; 37 import com.android.deskclock.events.Events; 38 import com.android.deskclock.provider.Alarm; 39 import com.android.deskclock.provider.AlarmInstance; 40 import com.android.deskclock.timer.TimerFragment; 41 import com.android.deskclock.timer.TimerService; 42 import com.android.deskclock.uidata.UiDataModel; 43 44 import java.util.ArrayList; 45 import java.util.Calendar; 46 import java.util.Date; 47 import java.util.Iterator; 48 import java.util.List; 49 50 import static android.text.format.DateUtils.SECOND_IN_MILLIS; 51 import static com.android.deskclock.AlarmSelectionActivity.ACTION_DISMISS; 52 import static com.android.deskclock.AlarmSelectionActivity.EXTRA_ACTION; 53 import static com.android.deskclock.AlarmSelectionActivity.EXTRA_ALARMS; 54 import static com.android.deskclock.provider.AlarmInstance.FIRED_STATE; 55 import static com.android.deskclock.provider.AlarmInstance.SNOOZE_STATE; 56 import static com.android.deskclock.uidata.UiDataModel.Tab.ALARMS; 57 import static com.android.deskclock.uidata.UiDataModel.Tab.TIMERS; 58 59 /** 60 * This activity is never visible. It processes all public intents defined by {@link AlarmClock} 61 * that apply to alarms and timers. Its definition in AndroidManifest.xml requires callers to hold 62 * the com.android.alarm.permission.SET_ALARM permission to complete the requested action. 63 */ 64 public class HandleApiCalls extends Activity { 65 66 private static final LogUtils.Logger LOGGER = new LogUtils.Logger("HandleApiCalls"); 67 68 private Context mAppContext; 69 70 @Override onCreate(Bundle icicle)71 protected void onCreate(Bundle icicle) { 72 super.onCreate(icicle); 73 74 mAppContext = getApplicationContext(); 75 76 try { 77 final Intent intent = getIntent(); 78 final String action = intent == null ? null : intent.getAction(); 79 if (action == null) { 80 return; 81 } 82 LOGGER.i("onCreate: " + intent); 83 84 switch (action) { 85 86 case AlarmClock.ACTION_SET_ALARM: 87 handleSetAlarm(intent); 88 break; 89 case AlarmClock.ACTION_SHOW_ALARMS: 90 handleShowAlarms(); 91 break; 92 case AlarmClock.ACTION_SET_TIMER: 93 handleSetTimer(intent); 94 break; 95 case AlarmClock.ACTION_SHOW_TIMERS: 96 handleShowTimers(intent); 97 break; 98 case AlarmClock.ACTION_DISMISS_ALARM: 99 handleDismissAlarm(intent); 100 break; 101 case AlarmClock.ACTION_SNOOZE_ALARM: 102 handleSnoozeAlarm(intent); 103 break; 104 case AlarmClock.ACTION_DISMISS_TIMER: 105 handleDismissTimer(intent); 106 break; 107 } 108 } catch (Exception e) { 109 LOGGER.wtf(e); 110 } finally { 111 finish(); 112 } 113 } 114 115 handleDismissAlarm(Intent intent)116 private void handleDismissAlarm(Intent intent) { 117 // Change to the alarms tab. 118 UiDataModel.getUiDataModel().setSelectedTab(ALARMS); 119 120 // Open DeskClock which is now positioned on the alarms tab. 121 startActivity(new Intent(mAppContext, DeskClock.class)); 122 123 new DismissAlarmAsync(mAppContext, intent, this).execute(); 124 } 125 dismissAlarm(Alarm alarm, Activity activity)126 public static void dismissAlarm(Alarm alarm, Activity activity) { 127 final Context context = activity.getApplicationContext(); 128 final AlarmInstance instance = AlarmInstance.getNextUpcomingInstanceByAlarmId( 129 context.getContentResolver(), alarm.id); 130 if (instance == null) { 131 final String reason = context.getString(R.string.no_alarm_scheduled_for_this_time); 132 Controller.getController().notifyVoiceFailure(activity, reason); 133 LOGGER.i("No alarm instance to dismiss"); 134 return; 135 } 136 137 dismissAlarmInstance(instance, activity); 138 } 139 dismissAlarmInstance(AlarmInstance instance, Activity activity)140 public static void dismissAlarmInstance(AlarmInstance instance, Activity activity) { 141 Utils.enforceNotMainLooper(); 142 143 final Context context = activity.getApplicationContext(); 144 final Date alarmTime = instance.getAlarmTime().getTime(); 145 final String time = DateFormat.getTimeFormat(context).format(alarmTime); 146 147 if (instance.mAlarmState == FIRED_STATE || instance.mAlarmState == SNOOZE_STATE) { 148 // Always dismiss alarms that are fired or snoozed. 149 AlarmStateManager.deleteInstanceAndUpdateParent(context, instance); 150 } else if (Utils.isAlarmWithin24Hours(instance)) { 151 // Upcoming alarms are always predismissed. 152 AlarmStateManager.setPreDismissState(context, instance); 153 } else { 154 // Otherwise the alarm cannot be dismissed at this time. 155 final String reason = context.getString( 156 R.string.alarm_cant_be_dismissed_still_more_than_24_hours_away, time); 157 Controller.getController().notifyVoiceFailure(activity, reason); 158 LOGGER.i("Can't dismiss alarm more than 24 hours in advance"); 159 } 160 161 // Log the successful dismissal. 162 final String reason = context.getString(R.string.alarm_is_dismissed, time); 163 Controller.getController().notifyVoiceSuccess(activity, reason); 164 LOGGER.i("Alarm dismissed: " + instance); 165 Events.sendAlarmEvent(R.string.action_dismiss, R.string.label_intent); 166 } 167 168 private static class DismissAlarmAsync extends AsyncTask<Void, Void, Void> { 169 170 private final Context mContext; 171 private final Intent mIntent; 172 private final Activity mActivity; 173 DismissAlarmAsync(Context context, Intent intent, Activity activity)174 public DismissAlarmAsync(Context context, Intent intent, Activity activity) { 175 mContext = context; 176 mIntent = intent; 177 mActivity = activity; 178 } 179 180 @Override doInBackground(Void... parameters)181 protected Void doInBackground(Void... parameters) { 182 final ContentResolver cr = mContext.getContentResolver(); 183 final List<Alarm> alarms = getEnabledAlarms(mContext); 184 if (alarms.isEmpty()) { 185 final String reason = mContext.getString(R.string.no_scheduled_alarms); 186 Controller.getController().notifyVoiceFailure(mActivity, reason); 187 LOGGER.i("No scheduled alarms"); 188 return null; 189 } 190 191 // remove Alarms in MISSED, DISMISSED, and PREDISMISSED states 192 for (Iterator<Alarm> i = alarms.iterator(); i.hasNext();) { 193 final AlarmInstance instance = AlarmInstance.getNextUpcomingInstanceByAlarmId( 194 cr, i.next().id); 195 if (instance == null || instance.mAlarmState > FIRED_STATE) { 196 i.remove(); 197 } 198 } 199 200 final String searchMode = mIntent.getStringExtra(AlarmClock.EXTRA_ALARM_SEARCH_MODE); 201 if (searchMode == null && alarms.size() > 1) { 202 // shows the UI where user picks which alarm they want to DISMISS 203 final Intent pickSelectionIntent = new Intent(mContext, 204 AlarmSelectionActivity.class) 205 .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK) 206 .putExtra(EXTRA_ACTION, ACTION_DISMISS) 207 .putExtra(EXTRA_ALARMS, alarms.toArray(new Parcelable[alarms.size()])); 208 mContext.startActivity(pickSelectionIntent); 209 final String voiceMessage = mContext.getString(R.string.pick_alarm_to_dismiss); 210 Controller.getController().notifyVoiceSuccess(mActivity, voiceMessage); 211 return null; 212 } 213 214 // fetch the alarms that are specified by the intent 215 final FetchMatchingAlarmsAction fmaa = 216 new FetchMatchingAlarmsAction(mContext, alarms, mIntent, mActivity); 217 fmaa.run(); 218 final List<Alarm> matchingAlarms = fmaa.getMatchingAlarms(); 219 220 // If there are multiple matching alarms and it wasn't expected 221 // disambiguate what the user meant 222 if (!AlarmClock.ALARM_SEARCH_MODE_ALL.equals(searchMode) && matchingAlarms.size() > 1) { 223 final Intent pickSelectionIntent = new Intent(mContext, AlarmSelectionActivity.class) 224 .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK) 225 .putExtra(EXTRA_ACTION, ACTION_DISMISS) 226 .putExtra(EXTRA_ALARMS, 227 matchingAlarms.toArray(new Parcelable[matchingAlarms.size()])); 228 mContext.startActivity(pickSelectionIntent); 229 final String voiceMessage = mContext.getString(R.string.pick_alarm_to_dismiss); 230 Controller.getController().notifyVoiceSuccess(mActivity, voiceMessage); 231 return null; 232 } 233 234 // Apply the action to the matching alarms 235 for (Alarm alarm : matchingAlarms) { 236 dismissAlarm(alarm, mActivity); 237 LOGGER.i("Alarm dismissed: " + alarm); 238 } 239 return null; 240 } 241 getEnabledAlarms(Context context)242 private static List<Alarm> getEnabledAlarms(Context context) { 243 final String selection = String.format("%s=?", Alarm.ENABLED); 244 final String[] args = { "1" }; 245 return Alarm.getAlarms(context.getContentResolver(), selection, args); 246 } 247 } 248 handleSnoozeAlarm(Intent intent)249 private void handleSnoozeAlarm(Intent intent) { 250 new SnoozeAlarmAsync(intent, this).execute(); 251 } 252 253 private static class SnoozeAlarmAsync extends AsyncTask<Void, Void, Void> { 254 255 private final Context mContext; 256 private final Intent mIntent; 257 private final Activity mActivity; 258 SnoozeAlarmAsync(Intent intent, Activity activity)259 public SnoozeAlarmAsync(Intent intent, Activity activity) { 260 mContext = activity.getApplicationContext(); 261 mIntent = intent; 262 mActivity = activity; 263 } 264 265 @Override doInBackground(Void... parameters)266 protected Void doInBackground(Void... parameters) { 267 final ContentResolver cr = mContext.getContentResolver(); 268 final List<AlarmInstance> alarmInstances = AlarmInstance.getInstancesByState( 269 cr, FIRED_STATE); 270 if (alarmInstances.isEmpty()) { 271 final String reason = mContext.getString(R.string.no_firing_alarms); 272 Controller.getController().notifyVoiceFailure(mActivity, reason); 273 LOGGER.i("No firing alarms"); 274 return null; 275 } 276 277 for (AlarmInstance firingAlarmInstance : alarmInstances) { 278 snoozeAlarm(firingAlarmInstance, mContext, mActivity); 279 } 280 return null; 281 } 282 } 283 snoozeAlarm(AlarmInstance alarmInstance, Context context, Activity activity)284 static void snoozeAlarm(AlarmInstance alarmInstance, Context context, Activity activity) { 285 Utils.enforceNotMainLooper(); 286 287 final String time = DateFormat.getTimeFormat(context).format( 288 alarmInstance.getAlarmTime().getTime()); 289 final String reason = context.getString(R.string.alarm_is_snoozed, time); 290 AlarmStateManager.setSnoozeState(context, alarmInstance, true); 291 292 Controller.getController().notifyVoiceSuccess(activity, reason); 293 LOGGER.i("Alarm snoozed: " + alarmInstance); 294 Events.sendAlarmEvent(R.string.action_snooze, R.string.label_intent); 295 } 296 297 /*** 298 * Processes the SET_ALARM intent 299 * @param intent Intent passed to the app 300 */ handleSetAlarm(Intent intent)301 private void handleSetAlarm(Intent intent) { 302 // Validate the hour, if one was given. 303 int hour = -1; 304 if (intent.hasExtra(AlarmClock.EXTRA_HOUR)) { 305 hour = intent.getIntExtra(AlarmClock.EXTRA_HOUR, hour); 306 if (hour < 0 || hour > 23) { 307 final int mins = intent.getIntExtra(AlarmClock.EXTRA_MINUTES, 0); 308 final String voiceMessage = getString(R.string.invalid_time, hour, mins, " "); 309 Controller.getController().notifyVoiceFailure(this, voiceMessage); 310 LOGGER.i("Illegal hour: " + hour); 311 return; 312 } 313 } 314 315 // Validate the minute, if one was given. 316 final int minutes = intent.getIntExtra(AlarmClock.EXTRA_MINUTES, 0); 317 if (minutes < 0 || minutes > 59) { 318 final String voiceMessage = getString(R.string.invalid_time, hour, minutes, " "); 319 Controller.getController().notifyVoiceFailure(this, voiceMessage); 320 LOGGER.i("Illegal minute: " + minutes); 321 return; 322 } 323 324 final boolean skipUi = intent.getBooleanExtra(AlarmClock.EXTRA_SKIP_UI, false); 325 final ContentResolver cr = getContentResolver(); 326 327 // If time information was not provided an existing alarm cannot be located and a new one 328 // cannot be created so show the UI for creating the alarm from scratch per spec. 329 if (hour == -1) { 330 // Change to the alarms tab. 331 UiDataModel.getUiDataModel().setSelectedTab(ALARMS); 332 333 // Intent has no time or an invalid time, open the alarm creation UI. 334 final Intent createAlarm = Alarm.createIntent(this, DeskClock.class, Alarm.INVALID_ID) 335 .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) 336 .putExtra(AlarmClockFragment.ALARM_CREATE_NEW_INTENT_EXTRA, true); 337 338 // Open DeskClock which is now positioned on the alarms tab. 339 startActivity(createAlarm); 340 final String voiceMessage = getString(R.string.invalid_time, hour, minutes, " "); 341 Controller.getController().notifyVoiceFailure(this, voiceMessage); 342 LOGGER.i("Missing alarm time; opening UI"); 343 return; 344 } 345 346 final StringBuilder selection = new StringBuilder(); 347 final List<String> argsList = new ArrayList<>(); 348 setSelectionFromIntent(intent, hour, minutes, selection, argsList); 349 350 // Try to locate an existing alarm using the intent data. 351 final String[] args = argsList.toArray(new String[argsList.size()]); 352 final List<Alarm> alarms = Alarm.getAlarms(cr, selection.toString(), args); 353 354 final Alarm alarm; 355 if (!alarms.isEmpty()) { 356 // Enable the first matching alarm. 357 alarm = alarms.get(0); 358 alarm.enabled = true; 359 Alarm.updateAlarm(cr, alarm); 360 361 // Delete all old instances. 362 AlarmStateManager.deleteAllInstances(this, alarm.id); 363 364 Events.sendAlarmEvent(R.string.action_update, R.string.label_intent); 365 LOGGER.i("Updated alarm: " + alarm); 366 } else { 367 // No existing alarm could be located; create one using the intent data. 368 alarm = new Alarm(); 369 updateAlarmFromIntent(alarm, intent); 370 alarm.deleteAfterUse = !alarm.daysOfWeek.isRepeating() && skipUi; 371 372 // Save the new alarm. 373 Alarm.addAlarm(cr, alarm); 374 375 Events.sendAlarmEvent(R.string.action_create, R.string.label_intent); 376 LOGGER.i("Created new alarm: " + alarm); 377 } 378 379 // Schedule the next instance. 380 final Calendar now = DataModel.getDataModel().getCalendar(); 381 final AlarmInstance alarmInstance = alarm.createInstanceAfter(now); 382 setupInstance(alarmInstance, skipUi); 383 384 final String time = DateFormat.getTimeFormat(this) 385 .format(alarmInstance.getAlarmTime().getTime()); 386 Controller.getController().notifyVoiceSuccess(this, getString(R.string.alarm_is_set, time)); 387 } 388 handleDismissTimer(Intent intent)389 private void handleDismissTimer(Intent intent) { 390 final Uri dataUri = intent.getData(); 391 if (dataUri != null) { 392 final Timer selectedTimer = getSelectedTimer(dataUri); 393 if (selectedTimer != null) { 394 DataModel.getDataModel().resetOrDeleteTimer(selectedTimer, R.string.label_intent); 395 Controller.getController().notifyVoiceSuccess(this, 396 getResources().getQuantityString(R.plurals.expired_timers_dismissed, 1)); 397 LOGGER.i("Timer dismissed: " + selectedTimer); 398 } else { 399 Controller.getController().notifyVoiceFailure(this, 400 getString(R.string.invalid_timer)); 401 LOGGER.e("Could not dismiss timer: invalid URI"); 402 } 403 } else { 404 final List<Timer> expiredTimers = DataModel.getDataModel().getExpiredTimers(); 405 if (!expiredTimers.isEmpty()) { 406 for (Timer timer : expiredTimers) { 407 DataModel.getDataModel().resetOrDeleteTimer(timer, R.string.label_intent); 408 } 409 final int numberOfTimers = expiredTimers.size(); 410 final String timersDismissedMessage = getResources().getQuantityString( 411 R.plurals.expired_timers_dismissed, numberOfTimers, numberOfTimers); 412 Controller.getController().notifyVoiceSuccess(this, timersDismissedMessage); 413 LOGGER.i(timersDismissedMessage); 414 } else { 415 Controller.getController().notifyVoiceFailure(this, 416 getString(R.string.no_expired_timers)); 417 LOGGER.e("Could not dismiss timer: no expired timers"); 418 } 419 } 420 } 421 getSelectedTimer(Uri dataUri)422 private Timer getSelectedTimer(Uri dataUri) { 423 try { 424 final int timerId = (int) ContentUris.parseId(dataUri); 425 return DataModel.getDataModel().getTimer(timerId); 426 } catch (NumberFormatException e) { 427 return null; 428 } 429 } 430 handleShowAlarms()431 private void handleShowAlarms() { 432 Events.sendAlarmEvent(R.string.action_show, R.string.label_intent); 433 434 // Open DeskClock positioned on the alarms tab. 435 UiDataModel.getUiDataModel().setSelectedTab(ALARMS); 436 startActivity(new Intent(this, DeskClock.class)); 437 } 438 handleShowTimers(Intent intent)439 private void handleShowTimers(Intent intent) { 440 Events.sendTimerEvent(R.string.action_show, R.string.label_intent); 441 442 final Intent showTimersIntent = new Intent(this, DeskClock.class); 443 444 final List<Timer> timers = DataModel.getDataModel().getTimers(); 445 if (!timers.isEmpty()) { 446 final Timer newestTimer = timers.get(timers.size() - 1); 447 showTimersIntent.putExtra(TimerService.EXTRA_TIMER_ID, newestTimer.getId()); 448 } 449 450 // Open DeskClock positioned on the timers tab. 451 UiDataModel.getUiDataModel().setSelectedTab(TIMERS); 452 startActivity(showTimersIntent); 453 } 454 handleSetTimer(Intent intent)455 private void handleSetTimer(Intent intent) { 456 // If no length is supplied, show the timer setup view. 457 if (!intent.hasExtra(AlarmClock.EXTRA_LENGTH)) { 458 // Change to the timers tab. 459 UiDataModel.getUiDataModel().setSelectedTab(TIMERS); 460 461 // Open DeskClock which is now positioned on the timers tab and show the timer setup. 462 startActivity(TimerFragment.createTimerSetupIntent(this)); 463 LOGGER.i("Showing timer setup"); 464 return; 465 } 466 467 // Verify that the timer length is between one second and one day. 468 final long lengthMillis = SECOND_IN_MILLIS * intent.getIntExtra(AlarmClock.EXTRA_LENGTH, 0); 469 if (lengthMillis < Timer.MIN_LENGTH) { 470 final String voiceMessage = getString(R.string.invalid_timer_length); 471 Controller.getController().notifyVoiceFailure(this, voiceMessage); 472 LOGGER.i("Invalid timer length requested: " + lengthMillis); 473 return; 474 } 475 476 final String label = getLabelFromIntent(intent, ""); 477 final boolean skipUi = intent.getBooleanExtra(AlarmClock.EXTRA_SKIP_UI, false); 478 479 // Attempt to reuse an existing timer that is Reset with the same length and label. 480 Timer timer = null; 481 for (Timer t : DataModel.getDataModel().getTimers()) { 482 if (!t.isReset()) { continue; } 483 if (t.getLength() != lengthMillis) { continue; } 484 if (!TextUtils.equals(label, t.getLabel())) { continue; } 485 486 timer = t; 487 break; 488 } 489 490 // Create a new timer if one could not be reused. 491 if (timer == null) { 492 timer = DataModel.getDataModel().addTimer(lengthMillis, label, skipUi); 493 Events.sendTimerEvent(R.string.action_create, R.string.label_intent); 494 } 495 496 // Start the selected timer. 497 DataModel.getDataModel().startTimer(timer); 498 Events.sendTimerEvent(R.string.action_start, R.string.label_intent); 499 Controller.getController().notifyVoiceSuccess(this, getString(R.string.timer_created)); 500 501 // If not instructed to skip the UI, display the running timer. 502 if (!skipUi) { 503 // Change to the timers tab. 504 UiDataModel.getUiDataModel().setSelectedTab(TIMERS); 505 506 // Open DeskClock which is now positioned on the timers tab. 507 startActivity(new Intent(this, DeskClock.class) 508 .putExtra(TimerService.EXTRA_TIMER_ID, timer.getId())); 509 } 510 } 511 setupInstance(AlarmInstance instance, boolean skipUi)512 private void setupInstance(AlarmInstance instance, boolean skipUi) { 513 instance = AlarmInstance.addInstance(this.getContentResolver(), instance); 514 AlarmStateManager.registerInstance(this, instance, true); 515 AlarmUtils.popAlarmSetToast(this, instance.getAlarmTime().getTimeInMillis()); 516 if (!skipUi) { 517 // Change to the alarms tab. 518 UiDataModel.getUiDataModel().setSelectedTab(ALARMS); 519 520 // Open DeskClock which is now positioned on the alarms tab. 521 final Intent showAlarm = Alarm.createIntent(this, DeskClock.class, instance.mAlarmId) 522 .putExtra(AlarmClockFragment.SCROLL_TO_ALARM_INTENT_EXTRA, instance.mAlarmId) 523 .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 524 startActivity(showAlarm); 525 } 526 } 527 528 /** 529 * @param alarm the alarm to be updated 530 * @param intent the intent containing new alarm field values to merge into the {@code alarm} 531 */ updateAlarmFromIntent(Alarm alarm, Intent intent)532 private static void updateAlarmFromIntent(Alarm alarm, Intent intent) { 533 alarm.enabled = true; 534 alarm.hour = intent.getIntExtra(AlarmClock.EXTRA_HOUR, alarm.hour); 535 alarm.minutes = intent.getIntExtra(AlarmClock.EXTRA_MINUTES, alarm.minutes); 536 alarm.vibrate = intent.getBooleanExtra(AlarmClock.EXTRA_VIBRATE, alarm.vibrate); 537 alarm.alert = getAlertFromIntent(intent, alarm.alert); 538 alarm.label = getLabelFromIntent(intent, alarm.label); 539 alarm.daysOfWeek = getDaysFromIntent(intent, alarm.daysOfWeek); 540 } 541 getLabelFromIntent(Intent intent, String defaultLabel)542 private static String getLabelFromIntent(Intent intent, String defaultLabel) { 543 final String message = intent.getExtras().getString(AlarmClock.EXTRA_MESSAGE, defaultLabel); 544 return message == null ? "" : message; 545 } 546 getDaysFromIntent(Intent intent, Weekdays defaultWeekdays)547 private static Weekdays getDaysFromIntent(Intent intent, Weekdays defaultWeekdays) { 548 if (!intent.hasExtra(AlarmClock.EXTRA_DAYS)) { 549 return defaultWeekdays; 550 } 551 552 final List<Integer> days = intent.getIntegerArrayListExtra(AlarmClock.EXTRA_DAYS); 553 if (days != null) { 554 final int[] daysArray = new int[days.size()]; 555 for (int i = 0; i < days.size(); i++) { 556 daysArray[i] = days.get(i); 557 } 558 return Weekdays.fromCalendarDays(daysArray); 559 } else { 560 // API says to use an ArrayList<Integer> but we allow the user to use a int[] too. 561 final int[] daysArray = intent.getIntArrayExtra(AlarmClock.EXTRA_DAYS); 562 if (daysArray != null) { 563 return Weekdays.fromCalendarDays(daysArray); 564 } 565 } 566 return defaultWeekdays; 567 } 568 getAlertFromIntent(Intent intent, Uri defaultUri)569 private static Uri getAlertFromIntent(Intent intent, Uri defaultUri) { 570 final String alert = intent.getStringExtra(AlarmClock.EXTRA_RINGTONE); 571 if (alert == null) { 572 return defaultUri; 573 } else if (AlarmClock.VALUE_RINGTONE_SILENT.equals(alert) || alert.isEmpty()) { 574 return Alarm.NO_RINGTONE_URI; 575 } 576 577 return Uri.parse(alert); 578 } 579 580 /** 581 * Assemble a database where clause to search for an alarm matching the given {@code hour} and 582 * {@code minutes} as well as all of the optional information within the {@code intent} 583 * including: 584 * 585 * <ul> 586 * <li>alarm message</li> 587 * <li>repeat days</li> 588 * <li>vibration setting</li> 589 * <li>ringtone uri</li> 590 * </ul> 591 * 592 * @param intent contains details of the alarm to be located 593 * @param hour the hour of the day of the alarm 594 * @param minutes the minute of the hour of the alarm 595 * @param selection an out parameter containing a SQL where clause 596 * @param args an out parameter containing the values to substitute into the {@code selection} 597 */ setSelectionFromIntent( Intent intent, int hour, int minutes, StringBuilder selection, List<String> args)598 private void setSelectionFromIntent( 599 Intent intent, 600 int hour, 601 int minutes, 602 StringBuilder selection, 603 List<String> args) { 604 selection.append(Alarm.HOUR).append("=?"); 605 args.add(String.valueOf(hour)); 606 selection.append(" AND ").append(Alarm.MINUTES).append("=?"); 607 args.add(String.valueOf(minutes)); 608 609 if (intent.hasExtra(AlarmClock.EXTRA_MESSAGE)) { 610 selection.append(" AND ").append(Alarm.LABEL).append("=?"); 611 args.add(getLabelFromIntent(intent, "")); 612 } 613 614 // Days is treated differently than other fields because if days is not specified, it 615 // explicitly means "not recurring". 616 selection.append(" AND ").append(Alarm.DAYS_OF_WEEK).append("=?"); 617 args.add(String.valueOf(getDaysFromIntent(intent, Weekdays.NONE).getBits())); 618 619 if (intent.hasExtra(AlarmClock.EXTRA_VIBRATE)) { 620 selection.append(" AND ").append(Alarm.VIBRATE).append("=?"); 621 args.add(intent.getBooleanExtra(AlarmClock.EXTRA_VIBRATE, false) ? "1" : "0"); 622 } 623 624 if (intent.hasExtra(AlarmClock.EXTRA_RINGTONE)) { 625 selection.append(" AND ").append(Alarm.RINGTONE).append("=?"); 626 627 // If the intent explicitly specified a NULL ringtone, treat it as the default ringtone. 628 final Uri defaultRingtone = DataModel.getDataModel().getDefaultAlarmRingtoneUri(); 629 final Uri ringtone = getAlertFromIntent(intent, defaultRingtone); 630 args.add(ringtone.toString()); 631 } 632 } 633 } 634