blob: c147ce3bacc17cf3b5b9fee171a3d3f68c04af7c [file] [log] [blame]
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.deskclock;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.AsyncTask;
import android.os.Bundle;
import android.preference.PreferenceManager;
import com.android.deskclock.events.Events;
import com.android.deskclock.stopwatch.StopwatchService;
import com.android.deskclock.stopwatch.Stopwatches;
import com.android.deskclock.timer.TimerFullScreenFragment;
import com.android.deskclock.timer.TimerObj;
import com.android.deskclock.worldclock.Cities;
import com.android.deskclock.worldclock.CitiesActivity;
import com.android.deskclock.worldclock.CityObj;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class HandleDeskClockApiCalls extends Activity {
private Context mAppContext;
private static final String ACTION_PREFIX = "com.android.deskclock.action.";
// shows the tab with world clocks
public static final String ACTION_SHOW_CLOCK = ACTION_PREFIX + "SHOW_CLOCK";
// add a clock of a selected city, if no city is specified opens the city selection screen
public static final String ACTION_ADD_CLOCK = ACTION_PREFIX + "ADD_CLOCK";
// delete a clock of a selected city, if no city is specified shows CitiesActivity for the user
// to choose a city
public static final String ACTION_DELETE_CLOCK = ACTION_PREFIX + "DELETE_CLOCK";
// extra for ACTION_ADD_CLOCK and ACTION_DELETE_CLOCK
public static final String EXTRA_CITY = "com.android.deskclock.extra.clock.CITY";
// shows the tab with the stopwatch
public static final String ACTION_SHOW_STOPWATCH = ACTION_PREFIX + "SHOW_STOPWATCH";
// starts the current stopwatch
public static final String ACTION_START_STOPWATCH = ACTION_PREFIX + "START_STOPWATCH";
// stops the current stopwatch
public static final String ACTION_STOP_STOPWATCH = ACTION_PREFIX + "STOP_STOPWATCH";
// laps the stopwatch that's currently running
public static final String ACTION_LAP_STOPWATCH = ACTION_PREFIX + "LAP_STOPWATCH";
// resets the stopwatch if it's stopped
public static final String ACTION_RESET_STOPWATCH = ACTION_PREFIX + "RESET_STOPWATCH";
// shows the tab with timers
public static final String ACTION_SHOW_TIMERS = ACTION_PREFIX + "SHOW_TIMERS";
// deletes the topmost timer
public static final String ACTION_DELETE_TIMER = ACTION_PREFIX + "DELETE_TIMER";
// stops the running timer
public static final String ACTION_STOP_TIMER = ACTION_PREFIX + "STOP_TIMER";
// starts the topmost timer
public static final String ACTION_START_TIMER = ACTION_PREFIX + "START_TIMER";
// resets the timer, works for both running and stopped
public static final String ACTION_RESET_TIMER = ACTION_PREFIX + "RESET_TIMER";
@Override
protected void onCreate(Bundle icicle) {
try {
super.onCreate(icicle);
mAppContext = getApplicationContext();
final Intent intent = getIntent();
if (intent == null) {
return;
}
final String action = intent.getAction();
switch (action) {
case ACTION_START_STOPWATCH:
case ACTION_STOP_STOPWATCH:
case ACTION_LAP_STOPWATCH:
case ACTION_SHOW_STOPWATCH:
case ACTION_RESET_STOPWATCH:
handleStopwatchIntent(action);
break;
case ACTION_SHOW_TIMERS:
case ACTION_DELETE_TIMER:
case ACTION_RESET_TIMER:
case ACTION_STOP_TIMER:
case ACTION_START_TIMER:
handleTimerIntent(action);
break;
case ACTION_SHOW_CLOCK:
case ACTION_ADD_CLOCK:
case ACTION_DELETE_CLOCK:
handleClockIntent(action);
break;
}
} finally {
finish();
}
}
private void handleStopwatchIntent(String action) {
// Opens the UI for stopwatch
final Intent stopwatchIntent = new Intent(mAppContext, DeskClock.class)
.setAction(action)
.putExtra(DeskClock.SELECT_TAB_INTENT_EXTRA, DeskClock.STOPWATCH_TAB_INDEX);
startActivity(stopwatchIntent);
LogUtils.i("HandleDeskClockApiCalls " + action);
if (action.equals(ACTION_SHOW_STOPWATCH)) {
Events.sendStopwatchEvent(R.string.action_show, R.string.label_intent);
return;
}
// checking if the stopwatch is already running
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mAppContext);
final boolean stopwatchAlreadyRunning =
prefs.getBoolean(Stopwatches.NOTIF_CLOCK_RUNNING, false);
if (stopwatchAlreadyRunning) {
// don't fire START_STOPWATCH or RESET_STOPWATCH if a stopwatch is already running
if (ACTION_START_STOPWATCH.equals(action)) {
final String reason = getString(R.string.stopwatch_already_running);
Voice.notifyFailure(this, reason);
LogUtils.i(reason);
return;
} else if (ACTION_RESET_STOPWATCH.equals(action)) { // RESET_STOPWATCH
final String reason = getString(R.string.stopwatch_cant_be_reset_because_is_running);
Voice.notifyFailure(this, reason);
LogUtils.i(reason);
return;
}
} else {
// if a stopwatch isn't running, don't try to stop or lap it
if (ACTION_STOP_STOPWATCH.equals(action) ||
ACTION_LAP_STOPWATCH.equals(action)) {
final String reason = getString(R.string.stopwatch_isnt_running);
Voice.notifyFailure(this, reason);
LogUtils.i(reason);
return;
}
}
final String reason;
// Events and voice interactor setup
switch (action) {
case ACTION_START_STOPWATCH:
Events.sendStopwatchEvent(R.string.action_start, R.string.label_intent);
reason = getString(R.string.stopwatch_started);
break;
case ACTION_STOP_STOPWATCH:
Events.sendStopwatchEvent(R.string.action_stop, R.string.label_intent);
reason = getString(R.string.stopwatch_stopped);
break;
case ACTION_LAP_STOPWATCH:
Events.sendStopwatchEvent(R.string.action_lap, R.string.label_intent);
reason = getString(R.string.stopwatch_lapped);
break;
case ACTION_RESET_STOPWATCH:
Events.sendStopwatchEvent(R.string.action_reset, R.string.label_intent);
reason = getString(R.string.stopwatch_reset);
break;
default:
return;
}
final Intent intent = new Intent(mAppContext, StopwatchService.class).setAction(action);
startService(intent);
Voice.notifySuccess(this, reason);
LogUtils.i(reason);
}
private void handleTimerIntent(final String action) {
// Opens the UI for timers
final Intent timerIntent = new Intent(mAppContext, DeskClock.class)
.putExtra(DeskClock.SELECT_TAB_INTENT_EXTRA, DeskClock.TIMER_TAB_INDEX)
.putExtra(TimerFullScreenFragment.GOTO_SETUP_VIEW, false);
startActivity(timerIntent);
LogUtils.i("HandleDeskClockApiCalls " + action);
if (ACTION_SHOW_TIMERS.equals(action)) {
Events.sendTimerEvent(R.string.action_show, R.string.label_intent);
return;
}
new HandleTimersAsync(mAppContext, action, this).execute();
}
private void handleClockIntent(final String action) {
// Opens the UI for clocks
final Intent handleClock = new Intent(mAppContext, DeskClock.class)
.setAction(action)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
.putExtra(DeskClock.SELECT_TAB_INTENT_EXTRA, DeskClock.CLOCK_TAB_INDEX);
startActivity(handleClock);
new HandleClockAsync(mAppContext, getIntent(), this).execute();
}
private static class HandleTimersAsync extends AsyncTask<Void, Void, Void> {
private final Context mContext;
private final String mAction;
private final Activity mActivity;
public HandleTimersAsync(Context context, String action, Activity activity) {
mContext = context;
mAction = action;
mActivity = activity;
}
// STOP_TIMER and START_TIMER should only be triggered if there is one timer that is
// not stopped or not started respectively. This method checks all timers to find only
// one that corresponds to that.
// Only change the mode of the timer if no disambiguation is necessary
@Override
protected Void doInBackground(Void... parameters) {
final List<TimerObj> timers = new ArrayList<>();
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mContext);
TimerObj.getTimersFromSharedPrefs(prefs, timers);
if (timers.isEmpty()) {
final String reason = mContext.getString(R.string.no_timer_set);
LogUtils.i(reason);
Voice.notifyFailure(mActivity, reason);
return null;
}
switch (mAction) {
case ACTION_DELETE_TIMER: {
// Delete a timer only if there's one available
if (timers.size() > 1) {
final String reason = mContext.getString(R.string.multiple_timers_available);
LogUtils.i(reason);
Voice.notifyFailure(mActivity, reason);
return null;
}
final TimerObj timer = timers.get(0);
timer.deleteFromSharedPref(prefs);
Events.sendTimerEvent(R.string.action_delete, R.string.label_intent);
final String reason = mContext.getString(R.string.timer_deleted);
Voice.notifySuccess(mActivity, reason);
LogUtils.i(reason);
break;
}
case ACTION_START_TIMER: {
final TimerObj timer = getTimerWithStateToIgnore(timers, TimerObj.STATE_RUNNING);
// Only start a timer if there's one non-running timer available
if (timer == null) {
// notifyFailure was already triggered
return null;
}
timer.setState(TimerObj.STATE_RUNNING);
timer.mStartTime = Utils.getTimeNow() - (timer.mSetupLength - timer.mTimeLeft);
timer.writeToSharedPref(prefs);
final String reason = mContext.getString(R.string.timer_started);
Voice.notifySuccess(mActivity, reason);
LogUtils.i(reason);
Events.sendTimerEvent(R.string.action_start, R.string.label_intent);
break;
}
case ACTION_RESET_TIMER: {
// Since timer can be reset only if it's stopped
// it's only triggered when there's only one stopped timer
final Set<Integer> statesToInclude = new HashSet<>();
statesToInclude.add(TimerObj.STATE_STOPPED);
final TimerObj timer = getTimerWithStatesToInclude(timers, statesToInclude,
mAction);
if (timer == null) {
return null;
}
final String reason = mContext.getString(R.string.timer_was_reset);
Voice.notifySuccess(mActivity, reason);
LogUtils.i(reason);
timer.setState(TimerObj.STATE_RESTART);
timer.mTimeLeft = timer.mOriginalLength;
timer.writeToSharedPref(prefs);
Events.sendTimerEvent(R.string.action_reset, R.string.label_intent);
break;
}
case ACTION_STOP_TIMER: {
final Set<Integer> statesToInclude = new HashSet<>();
statesToInclude.add(TimerObj.STATE_TIMESUP);
statesToInclude.add(TimerObj.STATE_RUNNING);
// Timer is stopped if there's only one running timer
final TimerObj timer = getTimerWithStatesToInclude(timers, statesToInclude,
mAction);
if (timer == null) {
return null;
}
final String reason = mContext.getString(R.string.timer_stopped);
LogUtils.i(reason);
Voice.notifySuccess(mActivity, reason);
timer.setState(TimerObj.STATE_STOPPED);
timer.writeToSharedPref(prefs);
Events.sendTimerEvent(R.string.action_stop, R.string.label_intent);
break;
}
}
return null;
}
/**
* @param timers available to the user
* @param stateToIgnore the opposite of the state that the timer should be in
* @return a timer only if there's one timer available that is of a state
* other than the state that's passed
* in all other cases returns null
*/
private TimerObj getTimerWithStateToIgnore(List<TimerObj> timers, int stateToIgnore) {
TimerObj soleTimer = null;
for (TimerObj timer : timers) {
if (timer.mState != stateToIgnore) {
if (soleTimer == null) {
soleTimer = timer;
} else {
// soleTimer has already been set
final String reason = mContext.getString(R.string.multiple_timers_available);
LogUtils.i(reason);
Voice.notifyFailure(mActivity, reason);
return null;
}
}
}
return soleTimer;
}
/**
* @param timers available to the user
* @param statesToInclude acceptable states of the timer
* @return a timer only if there's one timer available that is of the state
* that is passed in
* in all other cases returns null
*/
private TimerObj getTimerWithStatesToInclude(
List<TimerObj> timers, Set<Integer> statesToInclude, String action) {
TimerObj soleTimer = null;
for (TimerObj timer : timers) {
if (statesToInclude.contains(timer.mState)) {
if (soleTimer == null) {
soleTimer = timer;
} else {
// soleTimer has already been set
final String reason = mContext.getString(
R.string.multiple_timers_available);
LogUtils.i(reason);
Voice.notifyFailure(mActivity, reason);
return null;
}
}
}
// if there are no timers of desired property
// announce it to the user
if (soleTimer == null) {
if (action.equals(ACTION_RESET_TIMER)) {
// all timers are running
final String reason = mContext.getString(
R.string.timer_cant_be_reset_because_its_running);
LogUtils.i(reason);
Voice.notifyFailure(mActivity, reason);
} else if (action.equals(ACTION_STOP_TIMER)) {
// no running timers
final String reason = mContext.getString(R.string.timer_already_stopped);
LogUtils.i(reason);
Voice.notifyFailure(mActivity, reason);
}
}
return soleTimer;
}
}
private static class HandleClockAsync extends AsyncTask<Void, Void, Void> {
private final Context mContext;
private final Intent mIntent;
private final Activity mActivity;
public HandleClockAsync(Context context, Intent intent, Activity activity) {
mContext = context;
mIntent = intent;
mActivity = activity;
}
@Override
protected Void doInBackground(Void... parameters) {
final String cityExtra = mIntent.getStringExtra(EXTRA_CITY);
final SharedPreferences prefs =
PreferenceManager.getDefaultSharedPreferences(mContext);
switch (mIntent.getAction()) {
case ACTION_ADD_CLOCK: {
// if a city isn't specified open CitiesActivity to choose a city
if (cityExtra == null) {
final String reason = mContext.getString(R.string.no_city_selected);
Voice.notifyFailure(mActivity, reason);
LogUtils.i(reason);
mContext.startActivity(new Intent(mContext, CitiesActivity.class));
Events.sendClockEvent(R.string.action_create, R.string.label_intent);
break;
}
// if a city is passed add that city to the list
final Map<String, CityObj> cities = Utils.loadCityMapFromXml(mContext);
final CityObj city = cities.get(cityExtra.toLowerCase());
// check if this city exists in the list of available cities
if (city == null) {
final String reason = mContext.getString(
R.string.the_city_you_specified_is_not_available);
Voice.notifyFailure(mActivity, reason);
LogUtils.i(reason);
break;
}
final HashMap<String, CityObj> selectedCities =
Cities.readCitiesFromSharedPrefs(prefs);
// if this city is already added don't add it
if (selectedCities.put(city.mCityId, city) != null) {
final String reason = mContext.getString(R.string.the_city_already_added);
Voice.notifyFailure(mActivity, reason);
LogUtils.i(reason);
break;
}
Cities.saveCitiesToSharedPrefs(prefs, selectedCities);
final String reason = mContext.getString(R.string.city_added, city.mCityName);
Voice.notifySuccess(mActivity, reason);
LogUtils.i(reason);
Events.sendClockEvent(R.string.action_start, R.string.label_intent);
break;
}
case ACTION_DELETE_CLOCK: {
if (cityExtra == null) {
// if a city isn't specified open CitiesActivity to choose a city
final String reason = mContext.getString(R.string.no_city_selected);
Voice.notifyFailure(mActivity, reason);
LogUtils.i(reason);
mContext.startActivity(new Intent(mContext, CitiesActivity.class));
Events.sendClockEvent(R.string.action_create, R.string.label_intent);
break;
}
// if a city is specified check if it's selected and if so delete it
final Map<String, CityObj> cities = Utils.loadCityMapFromXml(mContext);
// check if this city exists in the list of available cities
final CityObj city = cities.get(cityExtra.toLowerCase());
if (city == null) {
final String reason = mContext.getString(
R.string.the_city_you_specified_is_not_available);
Voice.notifyFailure(mActivity, reason);
LogUtils.i(reason);
break;
}
final HashMap<String, CityObj> selectedCities =
Cities.readCitiesFromSharedPrefs(prefs);
if (selectedCities.remove(city.mCityId) != null) {
final String reason = mContext.getString(R.string.city_deleted,
city.mCityName);
Voice.notifySuccess(mActivity, reason);
LogUtils.i(reason);
Cities.saveCitiesToSharedPrefs(prefs, selectedCities);
Events.sendClockEvent(R.string.action_delete, R.string.label_intent);
} else {
// the specified city hasn't been added to the user's list yet
Voice.notifyFailure(mActivity, mContext.getString(
R.string.the_city_you_specified_is_not_available));
}
break;
}
case ACTION_SHOW_CLOCK:
Events.sendClockEvent(R.string.action_show, R.string.label_intent);
break;
}
return null;
}
}
}