blob: 49397801897c8f013fc8b7534b13905f42463d45 [file] [log] [blame]
/*
* Copyright (C) 2012 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.timer;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.app.Fragment;
import android.app.FragmentTransaction;
import android.app.NotificationManager;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.content.res.Resources;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.DecelerateInterpolator;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.FrameLayout;
import android.widget.ImageButton;
import android.widget.ListView;
import android.widget.TextView;
import com.android.deskclock.CircleButtonsLinearLayout;
import com.android.deskclock.DeskClock;
import com.android.deskclock.DeskClock.OnTapListener;
import com.android.deskclock.DeskClockFragment;
import com.android.deskclock.LabelDialogFragment;
import com.android.deskclock.R;
import com.android.deskclock.TimerSetupView;
import com.android.deskclock.Utils;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedList;
public class TimerFragment extends DeskClockFragment
implements OnClickListener, OnSharedPreferenceChangeListener {
private static final String TAG = "TimerFragment";
private static final String KEY_SETUP_SELECTED = "_setup_selected";
private static final String KEY_ENTRY_STATE = "entry_state";
private Bundle mViewState = null;
private ListView mTimersList;
private View mNewTimerPage;
private View mTimersListPage;
private Button mCancel, mStart;
private View mSeperator;
private ImageButton mAddTimer;
private View mTimerFooter;
private TimerSetupView mTimerSetup;
private TimersListAdapter mAdapter;
private boolean mTicking = false;
private SharedPreferences mPrefs;
private NotificationManager mNotificationManager;
private OnEmptyListListener mOnEmptyListListener;
private View mLastVisibleView = null; // used to decide if to set the view or animate to it.
public TimerFragment() {
}
class ClickAction {
public static final int ACTION_STOP = 1;
public static final int ACTION_PLUS_ONE = 2;
public static final int ACTION_DELETE = 3;
public int mAction;
public TimerObj mTimer;
public ClickAction(int action, TimerObj t) {
mAction = action;
mTimer = t;
}
}
// Container Activity that requests TIMESUP_MODE must implement this interface
public interface OnEmptyListListener {
public void onEmptyList();
public void onListChanged();
}
TimersListAdapter createAdapter(Context context, SharedPreferences prefs) {
if (mOnEmptyListListener == null) {
return new TimersListAdapter(context, prefs);
} else {
return new TimesUpListAdapter(context, prefs);
}
}
class TimersListAdapter extends BaseAdapter {
ArrayList<TimerObj> mTimers = new ArrayList<TimerObj> ();
Context mContext;
SharedPreferences mmPrefs;
public TimersListAdapter(Context context, SharedPreferences prefs) {
mContext = context;
mmPrefs = prefs;
}
@Override
public int getCount() {
return mTimers.size();
}
@Override
public Object getItem(int p) {
return mTimers.get(p);
}
@Override
public long getItemId(int p) {
if (p >= 0 && p < mTimers.size()) {
return mTimers.get(p).mTimerId;
}
return 0;
}
public void deleteTimer(int id) {
for (int i = 0; i < mTimers.size(); i++) {
TimerObj t = mTimers.get(i);
if (t.mTimerId == id) {
if (t.mView != null) {
((TimerListItem) t.mView).stop();
}
t.deleteFromSharedPref(mmPrefs);
mTimers.remove(i);
notifyDataSetChanged();
return;
}
}
}
protected int findTimerPositionById(int id) {
for (int i = 0; i < mTimers.size(); i++) {
TimerObj t = mTimers.get(i);
if (t.mTimerId == id) {
return i;
}
}
return -1;
}
public void removeTimer(TimerObj timerObj) {
int position = findTimerPositionById(timerObj.mTimerId);
if (position >= 0) {
mTimers.remove(position);
notifyDataSetChanged();
}
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
TimerListItem v = new TimerListItem (mContext); // TODO: Need to recycle convertView.
final TimerObj o = (TimerObj)getItem(position);
o.mView = v;
long timeLeft = o.updateTimeLeft(false);
boolean drawRed = o.mState != TimerObj.STATE_RESTART;
v.set(o.mOriginalLength, timeLeft, drawRed);
v.setTime(timeLeft, true);
switch (o.mState) {
case TimerObj.STATE_RUNNING:
v.start();
break;
case TimerObj.STATE_TIMESUP:
v.timesUp();
break;
case TimerObj.STATE_DONE:
v.done();
break;
default:
break;
}
// Timer text serves as a virtual start/stop button.
final CountingTimerView countingTimerView = (CountingTimerView)
v.findViewById(R.id.timer_time_text);
countingTimerView.registerVirtualButtonAction(new Runnable() {
@Override
public void run() {
TimerFragment.this.onClickHelper(
new ClickAction(ClickAction.ACTION_STOP, o));
}
});
ImageButton delete = (ImageButton)v.findViewById(R.id.timer_delete);
delete.setOnClickListener(TimerFragment.this);
delete.setTag(new ClickAction(ClickAction.ACTION_DELETE, o));
ImageButton plusOne = (ImageButton)v. findViewById(R.id.timer_plus_one);
plusOne.setOnClickListener(TimerFragment.this);
plusOne.setTag(new ClickAction(ClickAction.ACTION_PLUS_ONE, o));
TextView stop = (TextView)v. findViewById(R.id.timer_stop);
stop.setTag(new ClickAction(ClickAction.ACTION_STOP, o));
TimerFragment.this.setTimerButtons(o);
v.setBackgroundColor(getResources().getColor(R.color.blackish));
countingTimerView.registerStopTextView(stop);
CircleButtonsLinearLayout circleLayout =
(CircleButtonsLinearLayout)v.findViewById(R.id.timer_circle);
circleLayout.setCircleTimerViewIds(
R.id.timer_time, R.id.timer_plus_one, R.id.timer_delete, R.id.timer_stop,
R.dimen.plusone_reset_button_padding, R.dimen.delete_button_padding,
R.id.timer_label, R.id.timer_label_text);
FrameLayout label = (FrameLayout)v. findViewById(R.id.timer_label);
ImageButton labelIcon = (ImageButton)v. findViewById(R.id.timer_label_icon);
TextView labelText = (TextView)v. findViewById(R.id.timer_label_text);
if (o.mLabel.equals("")) {
labelText.setVisibility(View.GONE);
labelIcon.setVisibility(View.VISIBLE);
} else {
labelText.setText(o.mLabel);
labelText.setVisibility(View.VISIBLE);
labelIcon.setVisibility(View.GONE);
}
if (getActivity() instanceof DeskClock) {
label.setOnTouchListener(new OnTapListener(getActivity(), labelText) {
@Override
protected void processClick(View v) {
onLabelPressed(o);
}
});
} else {
labelIcon.setVisibility(View.INVISIBLE);
}
return v;
}
public void addTimer(TimerObj t) {
mTimers.add(0, t);
sort();
}
public void onSaveInstanceState(Bundle outState) {
TimerObj.putTimersInSharedPrefs(mmPrefs, mTimers);
}
public void onRestoreInstanceState(Bundle outState) {
TimerObj.getTimersFromSharedPrefs(mmPrefs, mTimers);
sort();
}
public void saveGlobalState() {
TimerObj.putTimersInSharedPrefs(mmPrefs, mTimers);
}
public void sort() {
if (getCount() > 0) {
Collections.sort(mTimers, mTimersCompare);
notifyDataSetChanged();
}
}
private final Comparator<TimerObj> mTimersCompare = new Comparator<TimerObj>() {
static final int BUZZING = 0;
static final int IN_USE = 1;
static final int NOT_USED = 2;
protected int getSection(TimerObj timerObj) {
switch (timerObj.mState) {
case TimerObj.STATE_TIMESUP:
return BUZZING;
case TimerObj.STATE_RUNNING:
case TimerObj.STATE_STOPPED:
return IN_USE;
default:
return NOT_USED;
}
}
@Override
public int compare(TimerObj o1, TimerObj o2) {
int section1 = getSection(o1);
int section2 = getSection(o2);
if (section1 != section2) {
return (section1 < section2) ? -1 : 1;
} else if (section1 == BUZZING || section1 == IN_USE) {
return (o1.mTimeLeft < o2.mTimeLeft) ? -1 : 1;
} else {
return (o1.mSetupLength < o2.mSetupLength) ? -1 : 1;
}
}
};
}
class TimesUpListAdapter extends TimersListAdapter {
public TimesUpListAdapter(Context context, SharedPreferences prefs) {
super(context, prefs);
}
@Override
public void onSaveInstanceState(Bundle outState) {
// This adapter has a data subset and never updates entire database
// Individual timers are updated in button handlers.
}
@Override
public void saveGlobalState() {
// This adapter has a data subset and never updates entire database
// Individual timers are updated in button handlers.
}
@Override
public void onRestoreInstanceState(Bundle outState) {
// This adapter loads a subset
TimerObj.getTimersFromSharedPrefs(mmPrefs, mTimers, TimerObj.STATE_TIMESUP);
if (getCount() == 0) {
mOnEmptyListListener.onEmptyList();
} else {
Collections.sort(mTimers, new Comparator<TimerObj>() {
@Override
public int compare(TimerObj o1, TimerObj o2) {
return (o1.mTimeLeft < o2.mTimeLeft) ? -1 : 1;
}
});
}
}
}
private final Runnable mClockTick = new Runnable() {
boolean mVisible = true;
final static int TIME_PERIOD_MS = 1000;
final static int SPLIT = TIME_PERIOD_MS / 2;
@Override
public void run() {
// Setup for blinking
boolean visible = Utils.getTimeNow() % TIME_PERIOD_MS < SPLIT;
boolean toggle = mVisible != visible;
mVisible = visible;
for (int i = 0; i < mAdapter.getCount(); i ++) {
TimerObj t = (TimerObj) mAdapter.getItem(i);
if (t.mState == TimerObj.STATE_RUNNING || t.mState == TimerObj.STATE_TIMESUP) {
long timeLeft = t.updateTimeLeft(false);
if ((TimerListItem)(t.mView) != null) {
((TimerListItem)(t.mView)).setTime(timeLeft, false);
}
}
if (t.mTimeLeft <= 0 && t.mState != TimerObj.STATE_DONE
&& t.mState != TimerObj.STATE_RESTART) {
t.mState = TimerObj.STATE_TIMESUP;
TimerFragment.this.setTimerButtons(t);
if ((TimerListItem)(t.mView) != null) {
((TimerListItem)(t.mView)).timesUp();
}
}
// The blinking
if (toggle && (TimerListItem)(t.mView) != null) {
if (t.mState == TimerObj.STATE_TIMESUP) {
((TimerListItem)(t.mView)).setCircleBlink(mVisible);
}
if (t.mState == TimerObj.STATE_STOPPED) {
((TimerListItem)(t.mView)).setTextBlink(mVisible);
}
}
}
mTimersList.postDelayed(mClockTick, 20);
}
};
@Override
public void onCreate(Bundle savedInstanceState) {
// Cache instance data and consume in first call to setupPage()
if (savedInstanceState != null) {
mViewState = savedInstanceState;
}
super.onCreate(savedInstanceState);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View v = inflater.inflate(R.layout.timer_fragment, container, false);
// Handle arguments from parent
Bundle bundle = getArguments();
if (bundle != null && bundle.containsKey(Timers.TIMESUP_MODE)) {
if (bundle.getBoolean(Timers.TIMESUP_MODE, false)) {
try {
mOnEmptyListListener = (OnEmptyListListener) getActivity();
} catch (ClassCastException e) {
Log.wtf(TAG, getActivity().toString() + " must implement OnEmptyListListener");
}
}
}
mTimersList = (ListView)v.findViewById(R.id.timers_list);
// Use light's out if this fragment is within the DeskClock
LayoutParams params;
float dividerHeight = getResources().getDimension(R.dimen.timer_divider_height);
if (getActivity() instanceof DeskClock) {
View footerView = inflater.inflate(R.layout.blank_footer_view, mTimersList, false);
params = footerView.getLayoutParams();
params.height -= dividerHeight;
footerView.setLayoutParams(params);
footerView.setBackgroundResource(R.color.blackish);
mTimersList.addFooterView(footerView);
} else {
mTimersList.setBackgroundColor(getResources().getColor(R.color.blackish));
}
// Make the top transparent header always visible so that the transition
// from the DeskClock app to the alert screen will be more pleasing visually.
View headerView = inflater.inflate(R.layout.blank_header_view, mTimersList, false);
params = headerView.getLayoutParams();
params.height -= dividerHeight;
headerView.setLayoutParams(params);
mTimersList.addHeaderView(headerView);
mNewTimerPage = v.findViewById(R.id.new_timer_page);
mTimersListPage = v.findViewById(R.id.timers_list_page);
mTimerSetup = (TimerSetupView)v.findViewById(R.id.timer_setup);
mSeperator = v.findViewById(R.id.timer_button_sep);
mCancel = (Button)v.findViewById(R.id.timer_cancel);
mCancel.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (mAdapter.getCount() != 0) {
gotoTimersView();
}
}
});
mStart = (Button)v.findViewById(R.id.timer_start);
mStart.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// New timer create if timer length is not zero
// Create a new timer object to track the timer and
// switch to the timers view.
int timerLength = mTimerSetup.getTime();
if (timerLength == 0) {
return;
}
TimerObj t = new TimerObj(timerLength * 1000);
t.mState = TimerObj.STATE_RUNNING;
mAdapter.addTimer(t);
updateTimersState(t, Timers.START_TIMER);
gotoTimersView();
mTimersList.setSelection(mAdapter.findTimerPositionById(t.mTimerId));
}
});
mTimerSetup.registerStartButton(mStart);
mAddTimer = (ImageButton)v.findViewById(R.id.timer_add_timer);
mAddTimer.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
mTimerSetup.reset();
gotoSetupView();
}
});
mTimerFooter = v.findViewById(R.id.timer_footer);
mTimerFooter.setVisibility(mOnEmptyListListener == null ? View.VISIBLE : View.GONE);
mPrefs = PreferenceManager.getDefaultSharedPreferences(getActivity());
mNotificationManager = (NotificationManager)
getActivity().getSystemService(Context.NOTIFICATION_SERVICE);
return v;
}
@Override
public void onDestroyView() {
mViewState = new Bundle();
saveViewState(mViewState);
super.onDestroyView();
}
@Override
public void onResume() {
if (getActivity() instanceof DeskClock) {
((DeskClock)getActivity()).registerPageChangedListener(this);
}
super.onResume();
mPrefs.registerOnSharedPreferenceChangeListener(this);
mAdapter = createAdapter(getActivity(), mPrefs);
mAdapter.onRestoreInstanceState(null);
if (mPrefs.getBoolean(Timers.FROM_NOTIFICATION, false)) {
// We need to know if this onresume is being called by the user clicking a
// buzzing timer notification. If so, we need to set that timer to have "stopped"
// at the moment the notification was hit.
long now = mPrefs.getLong(Timers.NOTIF_TIME, Utils.getTimeNow());
int timerId = mPrefs.getInt(Timers.NOTIF_ID, -1);
if (timerId != -1) {
TimerObj t = Timers.findTimer(mAdapter.mTimers, timerId);
t.mTimeLeft = t.mOriginalLength - (now - t.mStartTime);
cancelTimerNotification(timerId);
}
SharedPreferences.Editor editor = mPrefs.edit();
editor.putBoolean(Timers.FROM_NOTIFICATION, false);
editor.apply();
}
if (mPrefs.getBoolean(Timers.FROM_ALERT, false)) {
// Clear the flag set in the alert because the adapter was just
// created and thusly in sync with the database
SharedPreferences.Editor editor = mPrefs.edit();
editor.putBoolean(Timers.FROM_ALERT, false);
editor.apply();
}
mTimersList.setAdapter(mAdapter);
if (mAdapter.getCount() == 0) {
mCancel.setVisibility(View.GONE);
mSeperator.setVisibility(View.GONE);
}
mLastVisibleView = null; // Force a non animation setting of the view
setPage();
// View was hidden in onPause, make sure it is visible now.
View v = getView();
if (v != null) {
getView().setVisibility(View.VISIBLE);
}
}
@Override
public void onPause() {
if (getActivity() instanceof DeskClock) {
((DeskClock)getActivity()).unregisterPageChangedListener(this);
}
super.onPause();
stopClockTicks();
if (mAdapter != null) {
mAdapter.saveGlobalState ();
}
mPrefs.unregisterOnSharedPreferenceChangeListener(this);
// This is called because the lock screen was activated, the window stay
// active under it and when we unlock the screen, we see the old time for
// a fraction of a second.
View v = getView();
if (v != null) {
getView().setVisibility(View.INVISIBLE);
}
}
@Override
public void onPageChanged(int page) {
if (page == DeskClock.TIMER_TAB_INDEX && mAdapter != null) {
mAdapter.sort();
}
}
@Override
public void onSaveInstanceState (Bundle outState) {
super.onSaveInstanceState(outState);
if (mAdapter != null) {
mAdapter.onSaveInstanceState (outState);
}
if (mNewTimerPage != null) {
saveViewState(outState);
} else if (mViewState != null) {
outState.putAll(mViewState);
}
}
private void saveViewState(Bundle outState) {
outState.putBoolean(KEY_SETUP_SELECTED, mNewTimerPage.getVisibility() == View.VISIBLE);
mTimerSetup.saveEntryState(outState, KEY_ENTRY_STATE);
}
public void setPage() {
boolean switchToSetupView;
if (mViewState != null) {
switchToSetupView = mViewState.getBoolean(KEY_SETUP_SELECTED, false);
mTimerSetup.restoreEntryState(mViewState, KEY_ENTRY_STATE);
mViewState = null;
} else {
switchToSetupView = mAdapter.getCount() == 0;
}
if (switchToSetupView) {
gotoSetupView();
} else {
gotoTimersView();
}
}
public void stopAllTimesUpTimers() {
boolean notifyChange = false;
// To avoid race conditions where a timer was dismissed and it is still in the timers list
// and can be picked again, create a temporary list of timers to be removed first and
// then removed them one by one
LinkedList<TimerObj> timesupTimers = new LinkedList<TimerObj>();
for (int i = 0; i < mAdapter.getCount(); i ++) {
TimerObj timerObj = (TimerObj) mAdapter.getItem(i);
if (timerObj.mState == TimerObj.STATE_TIMESUP) {
timesupTimers.addFirst(timerObj);
notifyChange = true;
}
}
while (timesupTimers.size() > 0) {
onStopButtonPressed(timesupTimers.remove());
}
if (notifyChange) {
SharedPreferences.Editor editor = mPrefs.edit();
editor.putBoolean(Timers.FROM_ALERT, true);
editor.apply();
}
}
private void gotoSetupView() {
if (mLastVisibleView == null || mLastVisibleView.getId() == R.id.new_timer_page) {
mNewTimerPage.setVisibility(View.VISIBLE);
mNewTimerPage.setScaleX(1f);
mTimersListPage.setVisibility(View.GONE);
} else {
// Animate
ObjectAnimator a = ObjectAnimator.ofFloat(mTimersListPage, View.SCALE_X, 1f, 0f);
a.setInterpolator(new AccelerateInterpolator());
a.setDuration(125);
a.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mTimersListPage.setVisibility(View.GONE);
mNewTimerPage.setScaleX(0);
mNewTimerPage.setVisibility(View.VISIBLE);
ObjectAnimator b = ObjectAnimator.ofFloat(mNewTimerPage, View.SCALE_X, 0f, 1f);
b.setInterpolator(new DecelerateInterpolator());
b.setDuration(225);
b.start();
}
});
a.start();
}
stopClockTicks();
if (mAdapter.getCount() == 0) {
mCancel.setVisibility(View.GONE);
mSeperator.setVisibility(View.GONE);
} else {
mSeperator.setVisibility(View.VISIBLE);
mCancel.setVisibility(View.VISIBLE);
}
mTimerSetup.updateStartButton();
mTimerSetup.updateDeleteButton();
mLastVisibleView = mNewTimerPage;
}
private void gotoTimersView() {
if (mLastVisibleView == null || mLastVisibleView.getId() == R.id.timers_list_page) {
mNewTimerPage.setVisibility(View.GONE);
mTimersListPage.setVisibility(View.VISIBLE);
mTimersListPage.setScaleX(1f);
} else {
// Animate
ObjectAnimator a = ObjectAnimator.ofFloat(mNewTimerPage, View.SCALE_X, 1f, 0f);
a.setInterpolator(new AccelerateInterpolator());
a.setDuration(125);
a.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mNewTimerPage.setVisibility(View.GONE);
mTimersListPage.setScaleX(0);
mTimersListPage.setVisibility(View.VISIBLE);
ObjectAnimator b =
ObjectAnimator.ofFloat(mTimersListPage, View.SCALE_X, 0f, 1f);
b.setInterpolator(new DecelerateInterpolator());
b.setDuration(225);
b.start();
}
});
a.start();
}
startClockTicks();
mLastVisibleView = mTimersListPage;
}
@Override
public void onClick(View v) {
ClickAction tag = (ClickAction) v.getTag();
onClickHelper(tag);
}
private void onClickHelper(ClickAction clickAction) {
switch (clickAction.mAction) {
case ClickAction.ACTION_DELETE:
final TimerObj t = clickAction.mTimer;
if (t.mState == TimerObj.STATE_TIMESUP) {
cancelTimerNotification(t.mTimerId);
}
// Animate deletion, first alpha, then height
ObjectAnimator a = ObjectAnimator.ofFloat(t.mView, View.ALPHA, 1f, 0f);
a.setInterpolator(new AccelerateInterpolator());
a.setDuration(100);
a.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
ObjectAnimator b = ObjectAnimator.ofInt(
t.mView, "animatedHeight", t.mView.getHeight(), 0);
b.setInterpolator(new AccelerateInterpolator());
b.setDuration(200);
b.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mAdapter.deleteTimer(t.mTimerId);
if (mAdapter.getCount() == 0) {
if (mOnEmptyListListener == null) {
mTimerSetup.reset();
gotoSetupView();
} else {
mOnEmptyListListener.onEmptyList();
}
}
// Tell receiver the timer was deleted.
// It will stop all activity related to the
// timer
updateTimersState(t, Timers.DELETE_TIMER);
}
});
b.start();
}
});
a.start();
break;
case ClickAction.ACTION_PLUS_ONE:
onPlusOneButtonPressed(clickAction.mTimer);
setTimerButtons(clickAction.mTimer);
break;
case ClickAction.ACTION_STOP:
onStopButtonPressed(clickAction.mTimer);
setTimerButtons(clickAction.mTimer);
break;
default:
break;
}
}
private void onPlusOneButtonPressed(TimerObj t) {
switch(t.mState) {
case TimerObj.STATE_RUNNING:
t.addTime(60000); //60 seconds in millis
long timeLeft = t.updateTimeLeft(false);
((TimerListItem)(t.mView)).setTime(timeLeft, false);
((TimerListItem)(t.mView)).setLength(timeLeft);
mAdapter.notifyDataSetChanged();
updateTimersState(t, Timers.TIMER_UPDATE);
break;
case TimerObj.STATE_TIMESUP:
// +1 min when the time is up will restart the timer with 1 minute left.
t.mState = TimerObj.STATE_RUNNING;
t.mStartTime = Utils.getTimeNow();
t.mTimeLeft = t. mOriginalLength = 60000;
((TimerListItem)t.mView).setTime(t.mTimeLeft, false);
((TimerListItem)t.mView).set(t.mOriginalLength, t.mTimeLeft, true);
((TimerListItem) t.mView).start();
updateTimersState(t, Timers.TIMER_RESET);
updateTimersState(t, Timers.START_TIMER);
updateTimesUpMode(t);
cancelTimerNotification(t.mTimerId);
break;
case TimerObj.STATE_STOPPED:
case TimerObj.STATE_DONE:
t.mState = TimerObj.STATE_RESTART;
t.mTimeLeft = t. mOriginalLength = t.mSetupLength;
((TimerListItem)t.mView).stop();
((TimerListItem)t.mView).setTime(t.mTimeLeft, false);
((TimerListItem)t.mView).set(t.mOriginalLength, t.mTimeLeft, false);
updateTimersState(t, Timers.TIMER_RESET);
break;
default:
break;
}
}
private void onStopButtonPressed(TimerObj t) {
switch(t.mState) {
case TimerObj.STATE_RUNNING:
// Stop timer and save the remaining time of the timer
t.mState = TimerObj.STATE_STOPPED;
((TimerListItem) t.mView).pause();
t.updateTimeLeft(true);
updateTimersState(t, Timers.TIMER_STOP);
break;
case TimerObj.STATE_STOPPED:
// Reset the remaining time and continue timer
t.mState = TimerObj.STATE_RUNNING;
t.mStartTime = Utils.getTimeNow() - (t.mOriginalLength - t.mTimeLeft);
((TimerListItem) t.mView).start();
updateTimersState(t, Timers.START_TIMER);
break;
case TimerObj.STATE_TIMESUP:
t.mState = TimerObj.STATE_DONE;
// Used in a context where the timer could be off-screen and without a view
if (t.mView != null) {
((TimerListItem) t.mView).done();
}
updateTimersState(t, Timers.TIMER_DONE);
cancelTimerNotification(t.mTimerId);
updateTimesUpMode(t);
break;
case TimerObj.STATE_DONE:
break;
case TimerObj.STATE_RESTART:
t.mState = TimerObj.STATE_RUNNING;
t.mStartTime = Utils.getTimeNow() - (t.mOriginalLength - t.mTimeLeft);
((TimerListItem) t.mView).start();
updateTimersState(t, Timers.START_TIMER);
break;
default:
break;
}
}
private void onLabelPressed(TimerObj t) {
final FragmentTransaction ft = getFragmentManager().beginTransaction();
final Fragment prev = getFragmentManager().findFragmentByTag("label_dialog");
if (prev != null) {
ft.remove(prev);
}
ft.addToBackStack(null);
// Create and show the dialog.
final LabelDialogFragment newFragment =
LabelDialogFragment.newInstance(t, t.mLabel, getTag());
newFragment.show(ft, "label_dialog");
}
public void setLabel(TimerObj timer, String label) {
((TimerObj) mAdapter.getItem(
mAdapter.findTimerPositionById(timer.mTimerId))).mLabel = label;
if (timer.mState == TimerObj.STATE_TIMESUP) {
// Timer is in timesup mode.
TimerReceiver.showExpiredAlarmNotification(
getActivity().getApplicationContext(), timer);
}
mTimersList.invalidateViews();
}
private void setTimerButtons(TimerObj t) {
Context a = getActivity();
if (a == null || t == null || t.mView == null) {
return;
}
ImageButton plusOne = (ImageButton) t.mView.findViewById(R.id.timer_plus_one);
CountingTimerView countingTimerView = (CountingTimerView)
t.mView.findViewById(R.id.timer_time_text);
TextView stop = (TextView) t.mView.findViewById(R.id.timer_stop);
Resources r = a.getResources();
switch (t.mState) {
case TimerObj.STATE_RUNNING:
plusOne.setVisibility(View.VISIBLE);
plusOne.setContentDescription(r.getString(R.string.timer_plus_one));
plusOne.setImageResource(R.drawable.ic_plusone);
stop.setContentDescription(r.getString(R.string.timer_stop));
stop.setText(R.string.timer_stop);
stop.setTextColor(getResources().getColor(R.color.clock_white));
countingTimerView.setVirtualButtonEnabled(true);
break;
case TimerObj.STATE_STOPPED:
plusOne.setVisibility(View.VISIBLE);
plusOne.setContentDescription(r.getString(R.string.timer_reset));
plusOne.setImageResource(R.drawable.ic_reset);
stop.setContentDescription(r.getString(R.string.timer_start));
stop.setText(R.string.timer_start);
stop.setTextColor(getResources().getColor(R.color.clock_white));
countingTimerView.setVirtualButtonEnabled(true);
break;
case TimerObj.STATE_TIMESUP:
plusOne.setVisibility(View.VISIBLE);
plusOne.setImageResource(R.drawable.ic_plusone);
stop.setContentDescription(r.getString(R.string.timer_stop));
stop.setTextColor(getResources().getColor(R.color.clock_white));
countingTimerView.setVirtualButtonEnabled(true);
break;
case TimerObj.STATE_DONE:
plusOne.setVisibility(View.VISIBLE);
plusOne.setContentDescription(r.getString(R.string.timer_reset));
plusOne.setImageResource(R.drawable.ic_reset);
stop.setVisibility(View.INVISIBLE);
countingTimerView.setVirtualButtonEnabled(false);
break;
case TimerObj.STATE_RESTART:
plusOne.setVisibility(View.INVISIBLE);
stop.setVisibility(View.VISIBLE);
stop.setContentDescription(r.getString(R.string.timer_start));
stop.setText(R.string.timer_start);
stop.setTextColor(getResources().getColor(R.color.clock_white));
countingTimerView.setVirtualButtonEnabled(true);
break;
default:
break;
}
}
private void startClockTicks() {
mTimersList.postDelayed(mClockTick, 20);
mTicking = true;
}
private void stopClockTicks() {
if (mTicking) {
mTimersList.removeCallbacks(mClockTick);
mTicking = false;
}
}
private void updateTimersState(TimerObj t, String action) {
if (!Timers.DELETE_TIMER.equals(action)) {
t.writeToSharedPref(mPrefs);
}
Intent i = new Intent();
i.setAction(action);
i.putExtra(Timers.TIMER_INTENT_EXTRA, t.mTimerId);
getActivity().sendBroadcast(i);
}
private void cancelTimerNotification(int timerId) {
mNotificationManager.cancel(timerId);
}
private void updateTimesUpMode(TimerObj timerObj) {
if (mOnEmptyListListener != null && timerObj.mState != TimerObj.STATE_TIMESUP) {
mAdapter.removeTimer(timerObj);
if (mAdapter.getCount() == 0) {
mOnEmptyListListener.onEmptyList();
} else {
mOnEmptyListListener.onListChanged();
}
}
}
public void restartAdapter() {
mAdapter = createAdapter(getActivity(), mPrefs);
mAdapter.onRestoreInstanceState(null);
}
@Override
public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
if (prefs.equals(mPrefs)) {
if ( (key.equals(Timers.FROM_NOTIFICATION) || key.equals(Timers.NOTIF_ID)
|| key.equals(Timers.NOTIF_TIME)) &&
prefs.getBoolean(Timers.FROM_NOTIFICATION, false) ) {
// We need to know if the user has clicked the buzzing timer notification
// while the fragment is still open. If so, this listener will catch that event,
// and allow the timers to be re-instated based on the updated stop time.
// Because this method gets called with every change to the sharedprefs, we ensure
// that we only recalculate the timers if the change was specifically set by the
// user interacting with the notification.
long now = prefs.getLong(Timers.NOTIF_TIME, Utils.getTimeNow());
int timerId = prefs.getInt(Timers.NOTIF_ID, -1);
mAdapter = createAdapter(getActivity(), mPrefs);
mAdapter.onRestoreInstanceState(null);
if (timerId != -1) {
TimerObj t = Timers.findTimer(mAdapter.mTimers, timerId);
t.mTimeLeft = t.mOriginalLength - (now - t.mStartTime);
cancelTimerNotification(timerId);
}
mTimersList.setAdapter(mAdapter);
SharedPreferences.Editor editor = prefs.edit();
editor.putBoolean(Timers.FROM_NOTIFICATION, false);
editor.apply();
}
if (key.equals(Timers.FROM_ALERT) && prefs.getBoolean(Timers.FROM_ALERT, false)) {
// The flag was set in the alert so the adapter needs to re-sync
// with the database
SharedPreferences.Editor editor = prefs.edit();
editor.putBoolean(Timers.FROM_ALERT, false);
editor.apply();
mAdapter = createAdapter(getActivity(), mPrefs);
mAdapter.onRestoreInstanceState(null);
mTimersList.setAdapter(mAdapter);
}
}
}
}