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