• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2015 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.Context;
21 import android.content.Intent;
22 import android.content.SharedPreferences;
23 import android.os.AsyncTask;
24 import android.os.Bundle;
25 import android.preference.PreferenceManager;
26 
27 import com.android.deskclock.events.Events;
28 import com.android.deskclock.stopwatch.StopwatchService;
29 import com.android.deskclock.stopwatch.Stopwatches;
30 import com.android.deskclock.timer.TimerFullScreenFragment;
31 import com.android.deskclock.timer.TimerObj;
32 import com.android.deskclock.timer.Timers;
33 import com.android.deskclock.worldclock.Cities;
34 import com.android.deskclock.worldclock.CitiesActivity;
35 import com.android.deskclock.worldclock.CityObj;
36 
37 import java.util.ArrayList;
38 import java.util.HashMap;
39 import java.util.HashSet;
40 import java.util.List;
41 import java.util.Map;
42 import java.util.Set;
43 
44 public class HandleDeskClockApiCalls extends Activity {
45     private Context mAppContext;
46 
47     private static final String ACTION_PREFIX = "com.android.deskclock.action.";
48 
49     // shows the tab with world clocks
50     public static final String ACTION_SHOW_CLOCK = ACTION_PREFIX + "SHOW_CLOCK";
51     // add a clock of a selected city, if no city is specified opens the city selection screen
52     public static final String ACTION_ADD_CLOCK = ACTION_PREFIX + "ADD_CLOCK";
53     // delete a clock of a selected city, if no city is specified shows CitiesActivity for the user
54     // to choose a city
55     public static final String ACTION_DELETE_CLOCK = ACTION_PREFIX + "DELETE_CLOCK";
56     // extra for ACTION_ADD_CLOCK and ACTION_DELETE_CLOCK
57     public static final String EXTRA_CITY = "com.android.deskclock.extra.clock.CITY";
58 
59     // shows the tab with the stopwatch
60     public static final String ACTION_SHOW_STOPWATCH = ACTION_PREFIX + "SHOW_STOPWATCH";
61     // starts the current stopwatch
62     public static final String ACTION_START_STOPWATCH = ACTION_PREFIX + "START_STOPWATCH";
63     // stops the current stopwatch
64     public static final String ACTION_STOP_STOPWATCH = ACTION_PREFIX + "STOP_STOPWATCH";
65     // laps the stopwatch that's currently running
66     public static final String ACTION_LAP_STOPWATCH = ACTION_PREFIX + "LAP_STOPWATCH";
67     // resets the stopwatch if it's stopped
68     public static final String ACTION_RESET_STOPWATCH = ACTION_PREFIX + "RESET_STOPWATCH";
69 
70     // shows the tab with timers
71     public static final String ACTION_SHOW_TIMERS = ACTION_PREFIX + "SHOW_TIMERS";
72     // deletes the topmost timer
73     public static final String ACTION_DELETE_TIMER = ACTION_PREFIX + "DELETE_TIMER";
74     // stops the running timer
75     public static final String ACTION_STOP_TIMER = ACTION_PREFIX + "STOP_TIMER";
76     // starts the topmost timer
77     public static final String ACTION_START_TIMER = ACTION_PREFIX + "START_TIMER";
78     // resets the timer, works for both running and stopped
79     public static final String ACTION_RESET_TIMER = ACTION_PREFIX + "RESET_TIMER";
80 
81     @Override
onCreate(Bundle icicle)82     protected void onCreate(Bundle icicle) {
83         try {
84             super.onCreate(icicle);
85             mAppContext = getApplicationContext();
86 
87             final Intent intent = getIntent();
88             if (intent == null) {
89                 return;
90             }
91 
92             final String action = intent.getAction();
93             switch (action) {
94                 case ACTION_START_STOPWATCH:
95                 case ACTION_STOP_STOPWATCH:
96                 case ACTION_LAP_STOPWATCH:
97                 case ACTION_SHOW_STOPWATCH:
98                 case ACTION_RESET_STOPWATCH:
99                     handleStopwatchIntent(action);
100                     break;
101                 case ACTION_SHOW_TIMERS:
102                 case ACTION_DELETE_TIMER:
103                 case ACTION_RESET_TIMER:
104                 case ACTION_STOP_TIMER:
105                 case ACTION_START_TIMER:
106                     handleTimerIntent(action);
107                     break;
108                 case ACTION_SHOW_CLOCK:
109                 case ACTION_ADD_CLOCK:
110                 case ACTION_DELETE_CLOCK:
111                     handleClockIntent(action);
112                     break;
113             }
114         } finally {
115             finish();
116         }
117     }
118 
handleStopwatchIntent(String action)119     private void handleStopwatchIntent(String action) {
120         // Opens the UI for stopwatch
121         final Intent stopwatchIntent = new Intent(mAppContext, DeskClock.class)
122                 .setAction(action)
123                 .putExtra(DeskClock.SELECT_TAB_INTENT_EXTRA, DeskClock.STOPWATCH_TAB_INDEX);
124         startActivity(stopwatchIntent);
125         LogUtils.i("HandleDeskClockApiCalls " + action);
126 
127         if (action.equals(ACTION_SHOW_STOPWATCH)) {
128             Events.sendStopwatchEvent(R.string.action_show, R.string.label_intent);
129             return;
130         }
131 
132         // checking if the stopwatch is already running
133         final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mAppContext);
134         final boolean stopwatchAlreadyRunning =
135                 prefs.getBoolean(Stopwatches.NOTIF_CLOCK_RUNNING, false);
136 
137         if (stopwatchAlreadyRunning) {
138             // don't fire START_STOPWATCH or RESET_STOPWATCH if a stopwatch is already running
139             if (ACTION_START_STOPWATCH.equals(action)) {
140                 final String reason = getString(R.string.stopwatch_already_running);
141                 Voice.notifyFailure(this, reason);
142                 LogUtils.i(reason);
143                 return;
144             } else if (ACTION_RESET_STOPWATCH.equals(action)) { // RESET_STOPWATCH
145                 final String reason = getString(R.string.stopwatch_cant_be_reset_because_is_running);
146                 Voice.notifyFailure(this, reason);
147                 LogUtils.i(reason);
148                 return;
149             }
150         } else {
151             // if a stopwatch isn't running, don't try to stop or lap it
152             if (ACTION_STOP_STOPWATCH.equals(action) ||
153                     ACTION_LAP_STOPWATCH.equals(action)) {
154                 final String reason = getString(R.string.stopwatch_isnt_running);
155                 Voice.notifyFailure(this, reason);
156                 LogUtils.i(reason);
157                 return;
158             }
159         }
160 
161         final String reason;
162         // Events and voice interactor setup
163         switch (action) {
164             case ACTION_START_STOPWATCH:
165                 Events.sendStopwatchEvent(R.string.action_start, R.string.label_intent);
166                 reason = getString(R.string.stopwatch_started);
167                 break;
168             case ACTION_STOP_STOPWATCH:
169                 Events.sendStopwatchEvent(R.string.action_stop, R.string.label_intent);
170                 reason = getString(R.string.stopwatch_stopped);
171                 break;
172             case ACTION_LAP_STOPWATCH:
173                 Events.sendStopwatchEvent(R.string.action_lap, R.string.label_intent);
174                 reason = getString(R.string.stopwatch_lapped);
175                 break;
176             case ACTION_RESET_STOPWATCH:
177                 Events.sendStopwatchEvent(R.string.action_reset, R.string.label_intent);
178                 reason = getString(R.string.stopwatch_reset);
179                 break;
180             default:
181                 return;
182         }
183         final Intent intent = new Intent(mAppContext, StopwatchService.class).setAction(action);
184         startService(intent);
185         Voice.notifySuccess(this, reason);
186         LogUtils.i(reason);
187     }
188 
handleTimerIntent(final String action)189     private void handleTimerIntent(final String action) {
190         // Opens the UI for timers
191         final Intent timerIntent = new Intent(mAppContext, DeskClock.class)
192                 .putExtra(DeskClock.SELECT_TAB_INTENT_EXTRA, DeskClock.TIMER_TAB_INDEX)
193                 .putExtra(TimerFullScreenFragment.GOTO_SETUP_VIEW, false);
194         startActivity(timerIntent);
195         LogUtils.i("HandleDeskClockApiCalls " + action);
196 
197         if (ACTION_SHOW_TIMERS.equals(action)) {
198             Events.sendTimerEvent(R.string.action_show, R.string.label_intent);
199             return;
200         }
201         new HandleTimersAsync(mAppContext, action, this).execute();
202     }
203 
handleClockIntent(final String action)204     private void handleClockIntent(final String action) {
205         // Opens the UI for clocks
206         final Intent handleClock = new Intent(mAppContext, DeskClock.class)
207                 .setAction(action)
208                 .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
209                 .putExtra(DeskClock.SELECT_TAB_INTENT_EXTRA, DeskClock.CLOCK_TAB_INDEX);
210         startActivity(handleClock);
211 
212         new HandleClockAsync(mAppContext, getIntent(), this).execute();
213     }
214 
215     private static class HandleTimersAsync extends AsyncTask<Void, Void, Void> {
216         private final Context mContext;
217         private final String mAction;
218         private final Activity mActivity;
219 
HandleTimersAsync(Context context, String action, Activity activity)220         public HandleTimersAsync(Context context, String action, Activity activity) {
221             mContext = context;
222             mAction = action;
223             mActivity = activity;
224         }
225         // STOP_TIMER and START_TIMER should only be triggered if there is one timer that is
226         // not stopped or not started respectively. This method checks all timers to find only
227         // one that corresponds to that.
228         // Only change the mode of the timer if no disambiguation is necessary
229 
230         @Override
doInBackground(Void... parameters)231         protected Void doInBackground(Void... parameters) {
232             final List<TimerObj> timers = new ArrayList<>();
233             final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mContext);
234             TimerObj.getTimersFromSharedPrefs(prefs, timers);
235             if (timers.isEmpty()) {
236                 final String reason = mContext.getString(R.string.no_timer_set);
237                 LogUtils.i(reason);
238                 Voice.notifyFailure(mActivity, reason);
239                 return null;
240             }
241             final TimerObj timer;
242             final String timerAction;
243             switch (mAction) {
244                 case ACTION_DELETE_TIMER: {
245                     timerAction = Timers.DELETE_TIMER;
246                     // Delete a timer only if there's one available
247                     if (timers.size() > 1) {
248                         final String reason = mContext.getString(R.string.multiple_timers_available);
249                         LogUtils.i(reason);
250                         Voice.notifyFailure(mActivity, reason);
251                         return null;
252                     }
253 
254                     timer = timers.get(0);
255                     timer.deleteFromSharedPref(prefs);
256                     Events.sendTimerEvent(R.string.action_delete, R.string.label_intent);
257                     final String reason = mContext.getString(R.string.timer_deleted);
258                     Voice.notifySuccess(mActivity, reason);
259                     LogUtils.i(reason);
260                     break;
261                 }
262                 case ACTION_START_TIMER: {
263                     timerAction = Timers.START_TIMER;
264                     timer = getTimerWithStateToIgnore(timers, TimerObj.STATE_RUNNING);
265                     // Only start a timer if there's one non-running timer available
266                     if (timer == null) {
267                         // notifyFailure was already triggered
268                         return null;
269                     }
270                     timer.setState(TimerObj.STATE_RUNNING);
271                     timer.mStartTime = Utils.getTimeNow() - (timer.mSetupLength - timer.mTimeLeft);
272                     timer.writeToSharedPref(prefs);
273                     final String reason = mContext.getString(R.string.timer_started);
274                     Voice.notifySuccess(mActivity, reason);
275                     LogUtils.i(reason);
276                     Events.sendTimerEvent(R.string.action_start, R.string.label_intent);
277                     break;
278                 }
279                 case ACTION_RESET_TIMER: {
280                     timerAction = Timers.RESET_TIMER;
281                     // Since timer can be reset only if it's stopped
282                     // it's only triggered when there's only one stopped timer
283                     final Set<Integer> statesToInclude = new HashSet<>();
284                     statesToInclude.add(TimerObj.STATE_STOPPED);
285                     timer = getTimerWithStatesToInclude(timers, statesToInclude, mAction);
286                     if (timer == null) {
287                         return null;
288                     }
289                     final String reason = mContext.getString(R.string.timer_was_reset);
290                     Voice.notifySuccess(mActivity, reason);
291                     LogUtils.i(reason);
292                     timer.setState(TimerObj.STATE_RESTART);
293                     timer.mTimeLeft = timer.mSetupLength;
294                     timer.writeToSharedPref(prefs);
295                     Events.sendTimerEvent(R.string.action_reset, R.string.label_intent);
296                     break;
297                 }
298                 case ACTION_STOP_TIMER: {
299                     timerAction = Timers.STOP_TIMER;
300                     final Set<Integer> statesToInclude = new HashSet<>();
301                     statesToInclude.add(TimerObj.STATE_TIMESUP);
302                     statesToInclude.add(TimerObj.STATE_RUNNING);
303                     // Timer is stopped if there's only one running timer
304                     timer = getTimerWithStatesToInclude(timers, statesToInclude, mAction);
305                     if (timer == null) {
306                         return null;
307                     }
308                     final String reason = mContext.getString(R.string.timer_stopped);
309                     LogUtils.i(reason);
310                     Voice.notifySuccess(mActivity, reason);
311                     if (timer.mState == TimerObj.STATE_RUNNING) {
312                         timer.setState(TimerObj.STATE_STOPPED);
313                     } else {
314                         // if the time is up on the timer
315                         // restart it and reset the length
316                         timer.setState(TimerObj.STATE_RESTART);
317                         timer.mTimeLeft = timer.mSetupLength;
318                     }
319                     timer.writeToSharedPref(prefs);
320                     Events.sendTimerEvent(R.string.action_stop, R.string.label_intent);
321                     break;
322                 }
323                 default:
324                     return null;
325             }
326             // updating the time for next firing timer
327             final Intent i = new Intent()
328                     .setAction(timerAction)
329                     .putExtra(Timers.TIMER_INTENT_EXTRA, timer.mTimerId)
330                     .putExtra(Timers.UPDATE_NEXT_TIMESUP, true)
331                     // Make sure the receiver is getting the intent ASAP.
332                     .addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
333             mContext.sendBroadcast(i);
334             return null;
335         }
336 
337         /**
338          * @param timers available to the user
339          * @param stateToIgnore the opposite of the state that the timer should be in
340          * @return a timer only if there's one timer available that is of a state
341          * other than the state that's passed
342          * in all other cases returns null
343          */
getTimerWithStateToIgnore(List<TimerObj> timers, int stateToIgnore)344         private TimerObj getTimerWithStateToIgnore(List<TimerObj> timers, int stateToIgnore) {
345             TimerObj soleTimer = null;
346             for (TimerObj timer : timers) {
347                 if (timer.mState != stateToIgnore) {
348                     if (soleTimer == null) {
349                         soleTimer = timer;
350                     } else {
351                         // soleTimer has already been set
352                         final String reason = mContext.getString(R.string.multiple_timers_available);
353                         LogUtils.i(reason);
354                         Voice.notifyFailure(mActivity, reason);
355                         return null;
356                     }
357                 }
358             }
359             return soleTimer;
360         }
361 
362         /**
363          * @param timers available to the user
364          * @param statesToInclude acceptable states of the timer
365          * @return a timer only if there's one timer available that is of the state
366          * that is passed in
367          * in all other cases returns null
368          */
getTimerWithStatesToInclude( List<TimerObj> timers, Set<Integer> statesToInclude, String action)369         private TimerObj getTimerWithStatesToInclude(
370                 List<TimerObj> timers, Set<Integer> statesToInclude, String action) {
371             TimerObj soleTimer = null;
372             for (TimerObj timer : timers) {
373                 if (statesToInclude.contains(timer.mState)) {
374                     if (soleTimer == null) {
375                         soleTimer = timer;
376                     } else {
377                         // soleTimer has already been set
378                         final String reason = mContext.getString(
379                                 R.string.multiple_timers_available);
380                         LogUtils.i(reason);
381                         Voice.notifyFailure(mActivity, reason);
382                         return null;
383                     }
384                 }
385             }
386             // if there are no timers of desired property
387             // announce it to the user
388             if (soleTimer == null) {
389                 if (action.equals(ACTION_RESET_TIMER)) {
390                     // all timers are running
391                     final String reason = mContext.getString(
392                             R.string.timer_cant_be_reset_because_its_running);
393                     LogUtils.i(reason);
394                     Voice.notifyFailure(mActivity, reason);
395                 } else if (action.equals(ACTION_STOP_TIMER)) {
396                     // no running timers
397                     final String reason = mContext.getString(R.string.timer_already_stopped);
398                     LogUtils.i(reason);
399                     Voice.notifyFailure(mActivity, reason);
400                 }
401             }
402             return soleTimer;
403         }
404     }
405 
406     private static class HandleClockAsync extends AsyncTask<Void, Void, Void> {
407         private final Context mContext;
408         private final Intent mIntent;
409         private final Activity mActivity;
410 
HandleClockAsync(Context context, Intent intent, Activity activity)411         public HandleClockAsync(Context context, Intent intent, Activity activity) {
412             mContext = context;
413             mIntent = intent;
414             mActivity = activity;
415         }
416 
417         @Override
doInBackground(Void... parameters)418         protected Void doInBackground(Void... parameters) {
419             final String cityExtra = mIntent.getStringExtra(EXTRA_CITY);
420             final SharedPreferences prefs =
421                     PreferenceManager.getDefaultSharedPreferences(mContext);
422             switch (mIntent.getAction()) {
423                 case ACTION_ADD_CLOCK: {
424                     // if a city isn't specified open CitiesActivity to choose a city
425                     if (cityExtra == null) {
426                         final String reason = mContext.getString(R.string.no_city_selected);
427                         Voice.notifyFailure(mActivity, reason);
428                         LogUtils.i(reason);
429                         startCitiesActivity();
430                         Events.sendClockEvent(R.string.action_create, R.string.label_intent);
431                         break;
432                     }
433 
434                     // if a city is passed add that city to the list
435                     final Map<String, CityObj> cities = Utils.loadCityMapFromXml(mContext);
436                     final CityObj city = cities.get(cityExtra.toLowerCase());
437                     // check if this city exists in the list of available cities
438                     if (city == null) {
439                         final String reason = mContext.getString(
440                                 R.string.the_city_you_specified_is_not_available);
441                         Voice.notifyFailure(mActivity, reason);
442                         LogUtils.i(reason);
443                         break;
444                     }
445 
446                     final HashMap<String, CityObj> selectedCities =
447                             Cities.readCitiesFromSharedPrefs(prefs);
448                     // if this city is already added don't add it
449                     if (selectedCities.put(city.mCityId, city) != null) {
450                         final String reason = mContext.getString(R.string.the_city_already_added);
451                         Voice.notifyFailure(mActivity, reason);
452                         LogUtils.i(reason);
453                         break;
454                     }
455 
456                     Cities.saveCitiesToSharedPrefs(prefs, selectedCities);
457                     final String reason = mContext.getString(R.string.city_added, city.mCityName);
458                     Voice.notifySuccess(mActivity, reason);
459                     LogUtils.i(reason);
460                     Events.sendClockEvent(R.string.action_start, R.string.label_intent);
461                     break;
462                 }
463                 case ACTION_DELETE_CLOCK: {
464                     if (cityExtra == null) {
465                         // if a city isn't specified open CitiesActivity to choose a city
466                         final String reason = mContext.getString(R.string.no_city_selected);
467                         Voice.notifyFailure(mActivity, reason);
468                         LogUtils.i(reason);
469                         startCitiesActivity();
470                         Events.sendClockEvent(R.string.action_create, R.string.label_intent);
471                         break;
472                     }
473 
474                     // if a city is specified check if it's selected and if so delete it
475                     final Map<String, CityObj> cities = Utils.loadCityMapFromXml(mContext);
476                     // check if this city exists in the list of available cities
477                     final CityObj city = cities.get(cityExtra.toLowerCase());
478                     if (city == null) {
479                         final String reason = mContext.getString(
480                                 R.string.the_city_you_specified_is_not_available);
481                         Voice.notifyFailure(mActivity, reason);
482                         LogUtils.i(reason);
483                         break;
484                     }
485 
486                     final HashMap<String, CityObj> selectedCities =
487                             Cities.readCitiesFromSharedPrefs(prefs);
488                     if (selectedCities.remove(city.mCityId) != null) {
489                         final String reason = mContext.getString(R.string.city_deleted,
490                                 city.mCityName);
491                         Voice.notifySuccess(mActivity, reason);
492                         LogUtils.i(reason);
493                         Cities.saveCitiesToSharedPrefs(prefs, selectedCities);
494                         Events.sendClockEvent(R.string.action_delete, R.string.label_intent);
495                     } else {
496                         // the specified city hasn't been added to the user's list yet
497                         Voice.notifyFailure(mActivity, mContext.getString(
498                                 R.string.the_city_you_specified_is_not_available));
499                     }
500                     break;
501                 }
502                 case ACTION_SHOW_CLOCK:
503                     Events.sendClockEvent(R.string.action_show, R.string.label_intent);
504                     break;
505             }
506             return null;
507         }
508 
startCitiesActivity()509         private void startCitiesActivity() {
510             mContext.startActivity(new Intent(mContext, CitiesActivity.class).addFlags(
511                     Intent.FLAG_ACTIVITY_NEW_TASK));
512         }
513     }
514 }
515 
516