• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.Context;
22 import android.content.Intent;
23 import android.content.SharedPreferences;
24 import android.media.RingtoneManager;
25 import android.net.Uri;
26 import android.os.AsyncTask;
27 import android.os.Bundle;
28 import android.os.Looper;
29 import android.os.Parcelable;
30 import android.preference.PreferenceManager;
31 import android.provider.AlarmClock;
32 import android.text.TextUtils;
33 import android.text.format.DateFormat;
34 
35 import com.android.deskclock.alarms.AlarmStateManager;
36 import com.android.deskclock.events.Events;
37 import com.android.deskclock.provider.Alarm;
38 import com.android.deskclock.provider.AlarmInstance;
39 import com.android.deskclock.provider.DaysOfWeek;
40 import com.android.deskclock.timer.TimerFullScreenFragment;
41 import com.android.deskclock.timer.TimerObj;
42 import com.android.deskclock.timer.Timers;
43 
44 import java.util.ArrayList;
45 import java.util.Calendar;
46 import java.util.Iterator;
47 import java.util.List;
48 
49 public class HandleApiCalls extends Activity {
50 
51     public static final long TIMER_MIN_LENGTH = 1000;
52     public static final long TIMER_MAX_LENGTH = 24 * 60 * 60 * 1000;
53 
54     private Context mAppContext;
55 
56     @Override
onCreate(Bundle icicle)57     protected void onCreate(Bundle icicle) {
58         try {
59             super.onCreate(icicle);
60             mAppContext = getApplicationContext();
61             final Intent intent = getIntent();
62             final String action = intent == null ? null : intent.getAction();
63             if (action == null) {
64                 return;
65             }
66             switch (action) {
67                 case AlarmClock.ACTION_SET_ALARM:
68                     handleSetAlarm(intent);
69                     break;
70                 case AlarmClock.ACTION_SHOW_ALARMS:
71                     handleShowAlarms();
72                     break;
73                 case AlarmClock.ACTION_SET_TIMER:
74                     handleSetTimer(intent);
75                     break;
76                 case AlarmClock.ACTION_DISMISS_ALARM:
77                     handleDismissAlarm(intent.getAction());
78                     break;
79                 case AlarmClock.ACTION_SNOOZE_ALARM:
80                     handleSnoozeAlarm();
81             }
82         } finally {
83             finish();
84         }
85     }
86 
handleDismissAlarm(final String action)87     private void handleDismissAlarm(final String action) {
88         // Opens the UI for Alarms
89         final Intent alarmIntent =
90                 Alarm.createIntent(mAppContext, DeskClock.class, Alarm.INVALID_ID)
91                         .setAction(action)
92                         .putExtra(DeskClock.SELECT_TAB_INTENT_EXTRA, DeskClock.ALARM_TAB_INDEX);
93         startActivity(alarmIntent);
94 
95         final Intent intent = getIntent();
96 
97         new DismissAlarmAsync(mAppContext, intent, this).execute();
98     }
99 
dismissAlarm(Alarm alarm, Context context, Activity activity)100     public static void dismissAlarm(Alarm alarm, Context context, Activity activity) {
101         // only allow on background thread
102         if (Looper.myLooper() == Looper.getMainLooper()) {
103             throw new IllegalStateException("dismissAlarm must be called on a " +
104                     "background thread");
105         }
106 
107         final AlarmInstance alarmInstance = AlarmInstance.getNextUpcomingInstanceByAlarmId(
108                 context.getContentResolver(), alarm.id);
109         if (alarmInstance == null) {
110             final String reason = context.getString(R.string.no_alarm_scheduled_for_this_time);
111             Voice.notifyFailure(activity, reason);
112             LogUtils.i(reason);
113             return;
114         }
115 
116         final String time = DateFormat.getTimeFormat(context).format(
117                 alarmInstance.getAlarmTime().getTime());
118         if (Utils.isAlarmWithin24Hours(alarmInstance)) {
119             AlarmStateManager.setPreDismissState(context, alarmInstance);
120             final String reason = context.getString(R.string.alarm_is_dismissed, time);
121             LogUtils.i(reason);
122             Voice.notifySuccess(activity, reason);
123             Events.sendAlarmEvent(R.string.action_dismiss, R.string.label_intent);
124         } else {
125             final String reason = context.getString(
126                     R.string.alarm_cant_be_dismissed_still_more_than_24_hours_away, time);
127             Voice.notifyFailure(activity, reason);
128             LogUtils.i(reason);
129         }
130     }
131 
132     private static class DismissAlarmAsync extends AsyncTask<Void, Void, Void> {
133 
134         private final Context mContext;
135         private final Intent mIntent;
136         private final Activity mActivity;
137 
DismissAlarmAsync(Context context, Intent intent, Activity activity)138         public DismissAlarmAsync(Context context, Intent intent, Activity activity) {
139             mContext = context;
140             mIntent = intent;
141             mActivity = activity;
142         }
143 
144         @Override
doInBackground(Void... parameters)145         protected Void doInBackground(Void... parameters) {
146             final List<Alarm> alarms = getEnabledAlarms(mContext);
147             if (alarms.isEmpty()) {
148                 final String reason = mContext.getString(R.string.no_scheduled_alarms);
149                 LogUtils.i(reason);
150                 Voice.notifyFailure(mActivity, reason);
151                 return null;
152             }
153 
154             // remove Alarms in MISSED, DISMISSED, and PREDISMISSED states
155             for (Iterator<Alarm> i = alarms.iterator(); i.hasNext();) {
156                 final AlarmInstance alarmInstance = AlarmInstance.getNextUpcomingInstanceByAlarmId(
157                         mContext.getContentResolver(), i.next().id);
158                 if (alarmInstance == null ||
159                         alarmInstance.mAlarmState > AlarmInstance.FIRED_STATE) {
160                     i.remove();
161                 }
162             }
163 
164             final String searchMode = mIntent.getStringExtra(AlarmClock.EXTRA_ALARM_SEARCH_MODE);
165             if (searchMode == null && alarms.size() > 1) {
166                 // shows the UI where user picks which alarm they want to DISMISS
167                 final Intent pickSelectionIntent = new Intent(mContext,
168                         AlarmSelectionActivity.class)
169                         .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
170                         .putExtra(AlarmSelectionActivity.EXTRA_ALARMS,
171                                 alarms.toArray(new Parcelable[alarms.size()]));
172                 mContext.startActivity(pickSelectionIntent);
173                 Voice.notifySuccess(mActivity, mContext.getString(R.string.pick_alarm_to_dismiss));
174                 return null;
175             }
176 
177             // fetch the alarms that are specified by the intent
178             final FetchMatchingAlarmsAction fmaa =
179                     new FetchMatchingAlarmsAction(mContext, alarms, mIntent, mActivity);
180             fmaa.run();
181             final List<Alarm> matchingAlarms = fmaa.getMatchingAlarms();
182 
183             // If there are multiple matching alarms and it wasn't expected
184             // disambiguate what the user meant
185             if (!AlarmClock.ALARM_SEARCH_MODE_ALL.equals(searchMode) && matchingAlarms.size() > 1) {
186               final Intent pickSelectionIntent = new Intent(mContext, AlarmSelectionActivity.class)
187                         .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
188                         .putExtra(AlarmSelectionActivity.EXTRA_ALARMS,
189                                 matchingAlarms.toArray(new Parcelable[matchingAlarms.size()]));
190                 mContext.startActivity(pickSelectionIntent);
191                 Voice.notifySuccess(mActivity, mContext.getString(R.string.pick_alarm_to_dismiss));
192                 return null;
193             }
194 
195             // Apply the action to the matching alarms
196             for (Alarm alarm : matchingAlarms) {
197                 dismissAlarm(alarm, mContext, mActivity);
198                 LogUtils.i("Alarm %s is dismissed", alarm);
199             }
200             return null;
201         }
202 
getEnabledAlarms(Context context)203         private static List<Alarm> getEnabledAlarms(Context context) {
204             final String selection = String.format("%s=?", Alarm.ENABLED);
205             final String[] args = { "1" };
206             return Alarm.getAlarms(context.getContentResolver(), selection, args);
207         }
208     }
209 
handleSnoozeAlarm()210     private void handleSnoozeAlarm() {
211         new SnoozeAlarmAsync(mAppContext, this).execute();
212     }
213 
214     private static class SnoozeAlarmAsync extends AsyncTask<Void, Void, Void> {
215 
216         private final Context mContext;
217         private final Activity mActivity;
218 
SnoozeAlarmAsync(Context context, Activity activity)219         public SnoozeAlarmAsync(Context context, Activity activity) {
220             mContext = context;
221             mActivity = activity;
222         }
223 
224         @Override
doInBackground(Void... parameters)225         protected Void doInBackground(Void... parameters) {
226             final List<AlarmInstance> alarmInstances = AlarmInstance.getInstancesByState(
227                     mContext.getContentResolver(), AlarmInstance.FIRED_STATE);
228             if (alarmInstances.isEmpty()) {
229                 final String reason = mContext.getString(R.string.no_firing_alarms);
230                 LogUtils.i(reason);
231                 Voice.notifyFailure(mActivity, reason);
232                 return null;
233             }
234 
235             for (AlarmInstance firingAlarmInstance : alarmInstances) {
236                 snoozeAlarm(firingAlarmInstance, mContext, mActivity);
237             }
238             return null;
239         }
240     }
241 
snoozeAlarm(AlarmInstance alarmInstance, Context context, Activity activity)242     static void snoozeAlarm(AlarmInstance alarmInstance, Context context, Activity activity) {
243         // only allow on background thread
244         if (Looper.myLooper() == Looper.getMainLooper()) {
245             throw new IllegalStateException("snoozeAlarm must be called on a " +
246                     "background thread");
247         }
248         final String time = DateFormat.getTimeFormat(context).format(
249                 alarmInstance.getAlarmTime().getTime());
250         final String reason = context.getString(R.string.alarm_is_snoozed, time);
251         LogUtils.i(reason);
252         Voice.notifySuccess(activity, reason);
253         AlarmStateManager.setSnoozeState(context, alarmInstance, true);
254         LogUtils.i("Snooze %d:%d", alarmInstance.mHour, alarmInstance.mMinute);
255         Events.sendAlarmEvent(R.string.action_snooze, R.string.label_intent);
256     }
257 
258     /***
259      * Processes the SET_ALARM intent
260      * @param intent Intent passed to the app
261      */
handleSetAlarm(Intent intent)262     private void handleSetAlarm(Intent intent) {
263         // If not provided or invalid, show UI
264         final int hour = intent.getIntExtra(AlarmClock.EXTRA_HOUR, -1);
265 
266         // If not provided, use zero. If it is provided, make sure it's valid, otherwise, show UI
267         final int minutes;
268         if (intent.hasExtra(AlarmClock.EXTRA_MINUTES)) {
269             minutes = intent.getIntExtra(AlarmClock.EXTRA_MINUTES, -1);
270         } else {
271             minutes = 0;
272         }
273         if (hour < 0 || hour > 23 || minutes < 0 || minutes > 59) {
274             // Intent has no time or an invalid time, open the alarm creation UI
275             Intent createAlarm = Alarm.createIntent(this, DeskClock.class, Alarm.INVALID_ID);
276             createAlarm.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
277             createAlarm.putExtra(AlarmClockFragment.ALARM_CREATE_NEW_INTENT_EXTRA, true);
278             createAlarm.putExtra(DeskClock.SELECT_TAB_INTENT_EXTRA, DeskClock.ALARM_TAB_INDEX);
279             startActivity(createAlarm);
280             Voice.notifyFailure(this, getString(R.string.invalid_time, hour, minutes, " "));
281             LogUtils.i("HandleApiCalls no/invalid time; opening UI");
282             return;
283         }
284 
285         Events.sendAlarmEvent(R.string.action_create, R.string.label_intent);
286         final boolean skipUi = intent.getBooleanExtra(AlarmClock.EXTRA_SKIP_UI, false);
287 
288         final StringBuilder selection = new StringBuilder();
289         final List<String> args = new ArrayList<>();
290         setSelectionFromIntent(intent, hour, minutes, selection, args);
291 
292         final String message = getMessageFromIntent(intent);
293         final DaysOfWeek daysOfWeek = getDaysFromIntent(intent);
294         final boolean vibrate = intent.getBooleanExtra(AlarmClock.EXTRA_VIBRATE, false);
295         final String alert = intent.getStringExtra(AlarmClock.EXTRA_RINGTONE);
296 
297         Alarm alarm = new Alarm(hour, minutes);
298         alarm.enabled = true;
299         alarm.label = message;
300         alarm.daysOfWeek = daysOfWeek;
301         alarm.vibrate = vibrate;
302 
303         if (alert == null) {
304             alarm.alert = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_ALARM);
305         } else if (AlarmClock.VALUE_RINGTONE_SILENT.equals(alert) || alert.isEmpty()) {
306             alarm.alert = Alarm.NO_RINGTONE_URI;
307         } else {
308             alarm.alert = Uri.parse(alert);
309         }
310         alarm.deleteAfterUse = !daysOfWeek.isRepeating() && skipUi;
311 
312         final ContentResolver cr = getContentResolver();
313         alarm = Alarm.addAlarm(cr, alarm);
314         final AlarmInstance alarmInstance = alarm.createInstanceAfter(Calendar.getInstance());
315         setupInstance(alarmInstance, skipUi);
316         final String time = DateFormat.getTimeFormat(mAppContext).format(
317                 alarmInstance.getAlarmTime().getTime());
318         Voice.notifySuccess(this, getString(R.string.alarm_is_set, time));
319         LogUtils.i("HandleApiCalls set up alarm: %s", alarm);
320     }
321 
handleShowAlarms()322     private void handleShowAlarms() {
323         startActivity(new Intent(this, DeskClock.class)
324                 .putExtra(DeskClock.SELECT_TAB_INTENT_EXTRA, DeskClock.ALARM_TAB_INDEX));
325         Events.sendAlarmEvent(R.string.action_show, R.string.label_intent);
326         LogUtils.i("HandleApiCalls show alarms");
327     }
328 
handleSetTimer(Intent intent)329     private void handleSetTimer(Intent intent) {
330         final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
331         // If no length is supplied, show the timer setup view
332         if (!intent.hasExtra(AlarmClock.EXTRA_LENGTH)) {
333             startActivity(new Intent(this, DeskClock.class)
334                   .putExtra(DeskClock.SELECT_TAB_INTENT_EXTRA, DeskClock.TIMER_TAB_INDEX)
335                   .putExtra(TimerFullScreenFragment.GOTO_SETUP_VIEW, true));
336             LogUtils.i("HandleApiCalls showing timer setup");
337             return;
338         }
339 
340         final long length = 1000l * intent.getIntExtra(AlarmClock.EXTRA_LENGTH, 0);
341         if (length < TIMER_MIN_LENGTH || length > TIMER_MAX_LENGTH) {
342             Voice.notifyFailure(this, getString(R.string.invalid_timer_length));
343             LogUtils.i("Invalid timer length requested: " + length);
344             return;
345         }
346         String label = getMessageFromIntent(intent);
347 
348         TimerObj timer = null;
349         // Find an existing matching time
350         final List<TimerObj> timers = new ArrayList<>();
351         TimerObj.getTimersFromSharedPrefs(prefs, timers);
352         for (TimerObj t : timers) {
353             if (t.mSetupLength == length && (TextUtils.equals(label, t.mLabel))
354                     && t.mState == TimerObj.STATE_RESTART) {
355                 timer = t;
356                 break;
357             }
358         }
359 
360         boolean skipUi = intent.getBooleanExtra(AlarmClock.EXTRA_SKIP_UI, false);
361         if (timer == null) {
362             // Use a new timer
363             timer = new TimerObj(length, label, this /* context */);
364             // Timers set without presenting UI to the user will be deleted after use
365             timer.mDeleteAfterUse = skipUi;
366 
367             Events.sendTimerEvent(R.string.action_create, R.string.label_intent);
368         }
369 
370         timer.setState(TimerObj.STATE_RUNNING);
371         timer.mStartTime = Utils.getTimeNow();
372         timer.writeToSharedPref(prefs);
373 
374         Events.sendTimerEvent(R.string.action_start, R.string.label_intent);
375 
376         // Tell TimerReceiver that the timer was started
377         sendBroadcast(new Intent().setAction(Timers.START_TIMER)
378                 .putExtra(Timers.TIMER_INTENT_EXTRA, timer.mTimerId));
379 
380         if (skipUi) {
381             Utils.showInUseNotifications(this);
382         } else {
383             startActivity(new Intent(this, DeskClock.class)
384                     .putExtra(DeskClock.SELECT_TAB_INTENT_EXTRA, DeskClock.TIMER_TAB_INDEX)
385                     .putExtra(Timers.FIRST_LAUNCH_FROM_API_CALL, true)
386                     .putExtra(Timers.SCROLL_TO_TIMER_ID, timer.mTimerId));
387         }
388         Voice.notifySuccess(this, getString(R.string.timer_created));
389         LogUtils.i("HandleApiCalls timer created: %s", timer);
390     }
391 
setupInstance(AlarmInstance instance, boolean skipUi)392     private void setupInstance(AlarmInstance instance, boolean skipUi) {
393         instance = AlarmInstance.addInstance(this.getContentResolver(), instance);
394         AlarmStateManager.registerInstance(this, instance, true);
395         AlarmUtils.popAlarmSetToast(this, instance.getAlarmTime().getTimeInMillis());
396         if (!skipUi) {
397             Intent showAlarm = Alarm.createIntent(this, DeskClock.class, instance.mAlarmId);
398             showAlarm.putExtra(DeskClock.SELECT_TAB_INTENT_EXTRA, DeskClock.ALARM_TAB_INDEX);
399             showAlarm.putExtra(AlarmClockFragment.SCROLL_TO_ALARM_INTENT_EXTRA, instance.mAlarmId);
400             showAlarm.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
401             startActivity(showAlarm);
402         }
403     }
404 
getMessageFromIntent(Intent intent)405     private String getMessageFromIntent(Intent intent) {
406         final String message = intent.getStringExtra(AlarmClock.EXTRA_MESSAGE);
407         return message == null ? "" : message;
408     }
409 
getDaysFromIntent(Intent intent)410     private DaysOfWeek getDaysFromIntent(Intent intent) {
411         final DaysOfWeek daysOfWeek = new DaysOfWeek(0);
412         final ArrayList<Integer> days = intent.getIntegerArrayListExtra(AlarmClock.EXTRA_DAYS);
413         if (days != null) {
414             final int[] daysArray = new int[days.size()];
415             for (int i = 0; i < days.size(); i++) {
416                 daysArray[i] = days.get(i);
417             }
418             daysOfWeek.setDaysOfWeek(true, daysArray);
419         } else {
420             // API says to use an ArrayList<Integer> but we allow the user to use a int[] too.
421             final int[] daysArray = intent.getIntArrayExtra(AlarmClock.EXTRA_DAYS);
422             if (daysArray != null) {
423                 daysOfWeek.setDaysOfWeek(true, daysArray);
424             }
425         }
426         return daysOfWeek;
427     }
428 
setSelectionFromIntent( Intent intent, int hour, int minutes, StringBuilder selection, List<String> args)429     private void setSelectionFromIntent(
430             Intent intent,
431             int hour,
432             int minutes,
433             StringBuilder selection,
434             List<String> args) {
435         selection.append(Alarm.HOUR).append("=?");
436         args.add(String.valueOf(hour));
437         selection.append(" AND ").append(Alarm.MINUTES).append("=?");
438         args.add(String.valueOf(minutes));
439 
440         if (intent.hasExtra(AlarmClock.EXTRA_MESSAGE)) {
441             selection.append(" AND ").append(Alarm.LABEL).append("=?");
442             args.add(getMessageFromIntent(intent));
443         }
444 
445         // Days is treated differently that other fields because if days is not specified, it
446         // explicitly means "not recurring".
447         selection.append(" AND ").append(Alarm.DAYS_OF_WEEK).append("=?");
448         args.add(String.valueOf(intent.hasExtra(AlarmClock.EXTRA_DAYS)
449                 ? getDaysFromIntent(intent).getBitSet() : DaysOfWeek.NO_DAYS_SET));
450 
451         if (intent.hasExtra(AlarmClock.EXTRA_VIBRATE)) {
452             selection.append(" AND ").append(Alarm.VIBRATE).append("=?");
453             args.add(intent.getBooleanExtra(AlarmClock.EXTRA_VIBRATE, false) ? "1" : "0");
454         }
455 
456         if (intent.hasExtra(AlarmClock.EXTRA_RINGTONE)) {
457             selection.append(" AND ").append(Alarm.RINGTONE).append("=?");
458 
459             String ringTone = intent.getStringExtra(AlarmClock.EXTRA_RINGTONE);
460             if (ringTone == null) {
461                 // If the intent explicitly specified a NULL ringtone, treat it as the default
462                 // ringtone.
463                 ringTone = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_ALARM).toString();
464             } else if (AlarmClock.VALUE_RINGTONE_SILENT.equals(ringTone) || ringTone.isEmpty()) {
465                     ringTone = Alarm.NO_RINGTONE;
466             }
467             args.add(ringTone);
468         }
469     }
470 }
471