TimerFragment.java revision a65d3eb00d543735305221f86ce630c8650e65e2
1/*
2 * Copyright (C) 2012 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
17package com.android.deskclock.timer;
18
19import android.app.Fragment;
20import android.app.FragmentTransaction;
21import android.app.NotificationManager;
22import android.content.Context;
23import android.content.Intent;
24import android.content.SharedPreferences;
25import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
26import android.content.res.Resources;
27import android.os.Bundle;
28import android.preference.PreferenceManager;
29import android.util.Log;
30import android.view.LayoutInflater;
31import android.view.MotionEvent;
32import android.view.View;
33import android.view.View.OnClickListener;
34import android.view.View.OnTouchListener;
35import android.view.ViewGroup;
36import android.view.ViewGroup.LayoutParams;
37import android.widget.BaseAdapter;
38import android.widget.Button;
39import android.widget.FrameLayout;
40import android.widget.ImageButton;
41import android.widget.LinearLayout;
42import android.widget.ListView;
43import android.widget.TextView;
44
45import com.android.deskclock.DeskClock;
46import com.android.deskclock.CircleButtonsLinearLayout;
47import com.android.deskclock.DeskClockFragment;
48import com.android.deskclock.LabelDialogFragment;
49import com.android.deskclock.R;
50import com.android.deskclock.TimerSetupView;
51import com.android.deskclock.Utils;
52import com.android.deskclock.DeskClock.OnTapListener;
53
54import java.util.ArrayList;
55import java.util.Collections;
56import java.util.Comparator;
57
58
59public class TimerFragment extends DeskClockFragment
60        implements OnClickListener, OnSharedPreferenceChangeListener {
61
62    private static final String TAG = "TimerFragment";
63    private static final String KEY_SETUP_SELECTED = "_setup_selected";
64    private static final String KEY_ENTRY_STATE = "entry_state";
65    private Bundle mViewState = null;
66    private ListView mTimersList;
67    private View mNewTimerPage;
68    private View mTimersListPage;
69    private Button mCancel, mStart;
70    private View mSeperator;
71    private ImageButton mAddTimer;
72    private View mTimerFooter;
73    private TimerSetupView mTimerSetup;
74    private TimersListAdapter mAdapter;
75    private boolean mTicking = false;
76    private SharedPreferences mPrefs;
77    private NotificationManager mNotificationManager;
78    private OnEmptyListListener mOnEmptyListListener;
79
80    public TimerFragment() {
81    }
82
83    class ClickAction {
84        public static final int ACTION_STOP = 1;
85        public static final int ACTION_PLUS_ONE = 2;
86        public static final int ACTION_DELETE = 3;
87
88        public int mAction;
89        public TimerObj mTimer;
90
91        public ClickAction(int action, TimerObj t) {
92            mAction = action;
93            mTimer = t;
94        }
95    }
96
97    // Container Activity that requests TIMESUP_MODE must implement this interface
98    public interface OnEmptyListListener {
99        public void onEmptyList();
100    }
101
102    TimersListAdapter createAdapter(Context context, SharedPreferences prefs) {
103        if (mOnEmptyListListener == null) {
104            return new TimersListAdapter(context, prefs);
105        } else {
106            return new TimesUpListAdapter(context, prefs);
107        }
108    }
109
110    class TimersListAdapter extends BaseAdapter {
111
112        ArrayList<TimerObj> mTimers = new ArrayList<TimerObj> ();
113        private final LayoutInflater mInflater;
114        Context mContext;
115        SharedPreferences mmPrefs;
116
117        public TimersListAdapter(Context context, SharedPreferences prefs) {
118            mContext = context;
119            mInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
120            mmPrefs = prefs;
121        }
122
123        @Override
124        public int getCount() {
125            return mTimers.size();
126        }
127
128        @Override
129        public Object getItem(int p) {
130            return mTimers.get(p);
131        }
132
133        @Override
134        public long getItemId(int p) {
135            if (p >= 0 && p < mTimers.size()) {
136                return mTimers.get(p).mTimerId;
137            }
138            return 0;
139        }
140
141        public void deleteTimer(int id) {
142            for (int i = 0; i < mTimers.size(); i++) {
143                TimerObj t = mTimers.get(i);
144                if (t.mTimerId == id) {
145                    ((TimerListItem)t.mView).stop();
146                    t.deleteFromSharedPref(mmPrefs);
147                    mTimers.remove(i);
148                    notifyDataSetChanged();
149                    return;
150                }
151            }
152        }
153
154        protected int findTimerPositionById(int id) {
155            for (int i = 0; i < mTimers.size(); i++) {
156                TimerObj t = mTimers.get(i);
157                if (t.mTimerId == id) {
158                    return i;
159                }
160            }
161            return -1;
162        }
163
164        public void removeTimer(TimerObj timerObj) {
165            int position = findTimerPositionById(timerObj.mTimerId);
166            if (position >= 0) {
167                mTimers.remove(position);
168                notifyDataSetChanged();
169            }
170        }
171
172        @Override
173        public View getView(int position, View convertView, ViewGroup parent) {
174            TimerListItem v = new TimerListItem (mContext); // TODO: Need to recycle convertView.
175
176            final TimerObj o = (TimerObj)getItem(position);
177            o.mView = v;
178            long timeLeft =  o.updateTimeLeft(false);
179            boolean drawRed = o.mState != TimerObj.STATE_RESTART;
180            v.set(o.mOriginalLength, timeLeft, drawRed);
181            v.setTime(timeLeft, true);
182            switch (o.mState) {
183            case TimerObj.STATE_RUNNING:
184                v.start();
185                break;
186            case TimerObj.STATE_TIMESUP:
187                v.timesUp();
188                break;
189            case TimerObj.STATE_DONE:
190                v.done();
191                break;
192            default:
193                break;
194            }
195
196            // Timer text serves as a virtual start/stop button.
197            final CountingTimerView countingTimerView = (CountingTimerView)
198                    v.findViewById(R.id.timer_time_text);
199            countingTimerView.registerVirtualButtonAction(new Runnable() {
200                @Override
201                public void run() {
202                    TimerFragment.this.onClickHelper(
203                            new ClickAction(ClickAction.ACTION_STOP, o));
204                }
205            });
206
207            ImageButton delete = (ImageButton)v.findViewById(R.id.timer_delete);
208            delete.setOnClickListener(TimerFragment.this);
209            delete.setTag(new ClickAction(ClickAction.ACTION_DELETE, o));
210            ImageButton plusOne = (ImageButton)v. findViewById(R.id.timer_plus_one);
211            plusOne.setOnClickListener(TimerFragment.this);
212            plusOne.setTag(new ClickAction(ClickAction.ACTION_PLUS_ONE, o));
213            TextView stop = (TextView)v. findViewById(R.id.timer_stop);
214            stop.setTag(new ClickAction(ClickAction.ACTION_STOP, o));
215            TimerFragment.this.setTimerButtons(o);
216
217            v.setBackgroundColor(getResources().getColor(R.color.blackish));
218            countingTimerView.registerStopTextView(stop);
219            // Use light's out setup when this fragment is within the DeskClock
220            if (getActivity() instanceof DeskClock) {
221                countingTimerView.registerActivity((DeskClock) getActivity());
222            }
223            CircleButtonsLinearLayout circleLayout =
224                    (CircleButtonsLinearLayout)v.findViewById(R.id.timer_circle);
225            circleLayout.setCircleTimerViewIds(
226                    R.id.timer_time, R.id.timer_plus_one, R.id.timer_delete, R.id.timer_stop,
227                    R.dimen.plusone_reset_button_padding, R.dimen.delete_button_padding,
228                    R.id.timer_label, R.id.timer_label_text);
229
230            FrameLayout label = (FrameLayout)v. findViewById(R.id.timer_label);
231            ImageButton labelIcon = (ImageButton)v. findViewById(R.id.timer_label_icon);
232            TextView labelText = (TextView)v. findViewById(R.id.timer_label_text);
233            if (o.mLabel.equals("")) {
234                labelText.setVisibility(View.GONE);
235                labelIcon.setVisibility(View.VISIBLE);
236            } else {
237                labelText.setText(o.mLabel);
238                labelText.setVisibility(View.VISIBLE);
239                labelIcon.setVisibility(View.GONE);
240            }
241            if (getActivity() instanceof DeskClock) {
242                label.setOnTouchListener(new OnTapListener(getActivity(), labelText) {
243                    @Override
244                    protected void processClick(View v) {
245                        onLabelPressed(o);
246                    }
247                });
248            } else {
249                labelIcon.setVisibility(View.INVISIBLE);
250            }
251            return v;
252        }
253
254        public void addTimer(TimerObj t) {
255            mTimers.add(0, t);
256            notifyDataSetChanged();
257        }
258
259        public void onSaveInstanceState(Bundle outState) {
260            TimerObj.putTimersInSharedPrefs(mmPrefs, mTimers);
261        }
262
263        public void onRestoreInstanceState(Bundle outState) {
264            TimerObj.getTimersFromSharedPrefs(mmPrefs, mTimers);
265            notifyDataSetChanged();
266        }
267
268        public void saveGlobalState() {
269            TimerObj.putTimersInSharedPrefs(mmPrefs, mTimers);
270        }
271    }
272
273    class TimesUpListAdapter extends TimersListAdapter {
274
275        public TimesUpListAdapter(Context context, SharedPreferences prefs) {
276            super(context, prefs);
277        }
278
279        @Override
280        public void onSaveInstanceState(Bundle outState) {
281            // This adapter has a data subset and never updates entire database
282            // Individual timers are updated in button handlers.
283        }
284
285        @Override
286        public void saveGlobalState() {
287            // This adapter has a data subset and never updates entire database
288            // Individual timers are updated in button handlers.
289        }
290
291        @Override
292        public void onRestoreInstanceState(Bundle outState) {
293            // This adapter loads a subset
294            TimerObj.getTimersFromSharedPrefs(mmPrefs, mTimers, TimerObj.STATE_TIMESUP);
295
296            if (getCount() == 0) {
297                mOnEmptyListListener.onEmptyList();
298            } else {
299                Collections.sort(mTimers, new Comparator<TimerObj>() {
300                    @Override
301                    public int compare(TimerObj o1, TimerObj o2) {
302                       return (int)(o1.mTimeLeft - o2.mTimeLeft);
303                    }
304                });
305            }
306        }
307    }
308
309    private final Runnable mClockTick = new Runnable() {
310        boolean mVisible = true;
311        final static int TIME_PERIOD_MS = 1000;
312        final static int SPLIT = TIME_PERIOD_MS / 2;
313
314        @Override
315        public void run() {
316            // Setup for blinking
317            boolean visible = Utils.getTimeNow() % TIME_PERIOD_MS < SPLIT;
318            boolean toggle = mVisible != visible;
319            mVisible = visible;
320            for (int i = 0; i < mAdapter.getCount(); i ++) {
321                TimerObj t = (TimerObj) mAdapter.getItem(i);
322                if (t.mState == TimerObj.STATE_RUNNING || t.mState == TimerObj.STATE_TIMESUP) {
323                    long timeLeft = t.updateTimeLeft(false);
324                    if ((TimerListItem)(t.mView) != null) {
325                        ((TimerListItem)(t.mView)).setTime(timeLeft, false);
326                    }
327                }
328                if (t.mTimeLeft <= 0 && t.mState != TimerObj.STATE_DONE
329                        && t.mState != TimerObj.STATE_RESTART) {
330                    t.mState = TimerObj.STATE_TIMESUP;
331                    TimerFragment.this.setTimerButtons(t);
332                    if ((TimerListItem)(t.mView) != null) {
333                        ((TimerListItem)(t.mView)).timesUp();
334                    }
335                }
336
337                // The blinking
338                if (toggle && (TimerListItem)(t.mView) != null) {
339                    if (t.mState == TimerObj.STATE_TIMESUP) {
340                        ((TimerListItem)(t.mView)).setCircleBlink(mVisible);
341                    }
342                    if (t.mState == TimerObj.STATE_STOPPED) {
343                        ((TimerListItem)(t.mView)).setTextBlink(mVisible);
344                    }
345                }
346            }
347            mTimersList.postDelayed(mClockTick, 20);
348        }
349    };
350
351    @Override
352    public void onCreate(Bundle savedInstanceState) {
353        // Cache instance data and consume in first call to setupPage()
354        if (savedInstanceState != null) {
355            mViewState = savedInstanceState;
356        }
357
358        super.onCreate(savedInstanceState);
359    }
360
361    @Override
362    public View onCreateView(LayoutInflater inflater, ViewGroup container,
363                             Bundle savedInstanceState) {
364        // Inflate the layout for this fragment
365        View v = inflater.inflate(R.layout.timer_fragment, container, false);
366
367        // Handle arguments from parent
368        Bundle bundle = getArguments();
369        if (bundle != null && bundle.containsKey(Timers.TIMESUP_MODE)) {
370            if (bundle.getBoolean(Timers.TIMESUP_MODE, false)) {
371                try {
372                    mOnEmptyListListener = (OnEmptyListListener) getActivity();
373                } catch (ClassCastException e) {
374                    Log.wtf(TAG, getActivity().toString() + " must implement OnEmptyListListener");
375                }
376            }
377        }
378
379        mTimersList = (ListView)v.findViewById(R.id.timers_list);
380
381        // Use light's out if this fragment is within the DeskClock
382        if (getActivity() instanceof DeskClock) {
383            float dividerHeight = getResources().getDimension(R.dimen.timer_divider_height);
384            View footerView = inflater.inflate(R.layout.blank_footer_view, mTimersList, false);
385            LayoutParams params = footerView.getLayoutParams();
386            params.height -= dividerHeight;
387            footerView.setLayoutParams(params);
388            mTimersList.addFooterView(footerView);
389            View headerView = inflater.inflate(R.layout.blank_header_view, mTimersList, false);
390            params = headerView.getLayoutParams();
391            params.height -= dividerHeight;
392            headerView.setLayoutParams(params);
393            mTimersList.addHeaderView(headerView);
394            mTimersList.setOnTouchListener(new OnTapListener(getActivity()));
395            footerView.setOnClickListener(new OnClickListener() {
396                @Override
397                public void onClick(View v) {
398                    ((DeskClock) getActivity()).clockOnViewClick(v);
399                }
400            });
401            headerView.setOnClickListener(new OnClickListener() {
402                @Override
403                public void onClick(View v) {
404                    ((DeskClock) getActivity()).clockOnViewClick(v);
405                }
406            });
407        } else {
408            mTimersList.setBackgroundColor(getResources().getColor(R.color.blackish));
409        }
410
411        mNewTimerPage = v.findViewById(R.id.new_timer_page);
412        mTimersListPage = v.findViewById(R.id.timers_list_page);
413        mTimerSetup = (TimerSetupView)v.findViewById(R.id.timer_setup);
414        mSeperator = v.findViewById(R.id.timer_button_sep);
415        mCancel = (Button)v.findViewById(R.id.timer_cancel);
416        mCancel.setOnClickListener(new OnClickListener() {
417            @Override
418            public void onClick(View v) {
419                if (mAdapter.getCount() != 0) {
420                    gotoTimersView();
421                }
422            }
423        });
424        mStart = (Button)v.findViewById(R.id.timer_start);
425        mStart.setOnClickListener(new OnClickListener() {
426            @Override
427            public void onClick(View v) {
428                // New timer create if timer length is not zero
429                // Create a new timer object to track the timer and
430                // switch to the timers view.
431                int timerLength = mTimerSetup.getTime();
432                if (timerLength == 0) {
433                    return;
434                }
435                TimerObj t = new TimerObj(timerLength * 1000);
436                t.mState = TimerObj.STATE_RUNNING;
437                mAdapter.addTimer(t);
438                updateTimersState(t, Timers.START_TIMER);
439                gotoTimersView();
440            }
441
442        });
443        mAddTimer = (ImageButton)v.findViewById(R.id.timer_add_timer);
444        mAddTimer.setOnClickListener(new OnClickListener() {
445            @Override
446            public void onClick(View v) {
447                // This is unchecked because mAddTimer only used within DeskClock
448                DeskClock activity = ((DeskClock) getActivity());
449                if (!activity.isClockStateNormal()) {
450                    activity.bringLightsUp(true);
451                    return;
452                }
453                mTimerSetup.reset();
454                gotoSetupView();
455            }
456
457        });
458        mTimerFooter = v.findViewById(R.id.timer_footer);
459        mTimerFooter.setVisibility(mOnEmptyListListener == null ? View.VISIBLE : View.GONE);
460        mPrefs = PreferenceManager.getDefaultSharedPreferences(getActivity());
461        mNotificationManager = (NotificationManager)
462                getActivity().getSystemService(Context.NOTIFICATION_SERVICE);
463
464        return v;
465    }
466
467    @Override
468    public void onDestroyView() {
469        mViewState = new Bundle();
470        saveViewState(mViewState);
471        super.onDestroyView();
472    }
473
474    @Override
475    public void onResume() {
476        super.onResume();
477        mPrefs.registerOnSharedPreferenceChangeListener(this);
478
479        mAdapter = createAdapter(getActivity(), mPrefs);
480        mAdapter.onRestoreInstanceState(null);
481
482        if (mPrefs.getBoolean(Timers.FROM_NOTIFICATION, false)) {
483            // We need to know if this onresume is being called by the user clicking a
484            // buzzing timer notification. If so, we need to set that timer to have "stopped"
485            // at the moment the notification was hit.
486            long now = mPrefs.getLong(Timers.NOTIF_TIME, Utils.getTimeNow());
487            int timerId = mPrefs.getInt(Timers.NOTIF_ID, -1);
488            if (timerId != -1) {
489                TimerObj t = Timers.findTimer(mAdapter.mTimers, timerId);
490                t.mTimeLeft = t.mOriginalLength - (now - t.mStartTime);
491                cancelTimerNotification(timerId);
492            }
493            SharedPreferences.Editor editor = mPrefs.edit();
494            editor.putBoolean(Timers.FROM_NOTIFICATION, false);
495            editor.apply();
496        }
497
498        mTimersList.setAdapter(mAdapter);
499        if (mAdapter.getCount() == 0) {
500            mCancel.setVisibility(View.GONE);
501            mSeperator.setVisibility(View.GONE);
502        }
503        setPage();
504    }
505
506    @Override
507    public void onPause() {
508        super.onPause();
509        stopClockTicks();
510        if (mAdapter != null) {
511            mAdapter.saveGlobalState ();
512        }
513        mPrefs.unregisterOnSharedPreferenceChangeListener(this);
514    }
515
516    @Override
517    public void onSaveInstanceState (Bundle outState) {
518        super.onSaveInstanceState(outState);
519        if (mAdapter != null) {
520            mAdapter.onSaveInstanceState (outState);
521        }
522        if (mNewTimerPage != null) {
523            saveViewState(outState);
524        } else if (mViewState != null) {
525            outState.putAll(mViewState);
526        }
527    }
528
529    private void saveViewState(Bundle outState) {
530        outState.putBoolean(KEY_SETUP_SELECTED, mNewTimerPage.getVisibility() == View.VISIBLE);
531        mTimerSetup.saveEntryState(outState, KEY_ENTRY_STATE);
532    }
533
534    public void setPage() {
535        boolean switchToSetupView = mAdapter.getCount() == 0;
536        if (mViewState != null) {
537            switchToSetupView |= mViewState.getBoolean(KEY_SETUP_SELECTED, false);
538            mTimerSetup.restoreEntryState(mViewState, KEY_ENTRY_STATE);
539            mViewState = null;
540        }
541        if (switchToSetupView) {
542            gotoSetupView();
543        } else {
544            gotoTimersView();
545        }
546    }
547
548    public void stopAllTimesUpTimers() {
549        while (0 < mAdapter.getCount()) {
550            TimerObj timerObj = (TimerObj) mAdapter.getItem(0);
551            if (timerObj.mState == TimerObj.STATE_TIMESUP) {
552                onStopButtonPressed(timerObj);
553            }
554        }
555    }
556
557    private void gotoSetupView() {
558        mNewTimerPage.setVisibility(View.VISIBLE);
559        mTimersListPage.setVisibility(View.GONE);
560        stopClockTicks();
561        if (mAdapter.getCount() == 0) {
562            mCancel.setVisibility(View.GONE);
563            mSeperator.setVisibility(View.GONE);
564        } else {
565            mSeperator.setVisibility(View.VISIBLE);
566            mCancel.setVisibility(View.VISIBLE);
567        }
568        // Use light's out setup when this fragment is within the DeskClock
569        if (getActivity() instanceof DeskClock) {
570            ((DeskClock) getActivity()).setTimerAddingTimerState(true);
571            ((DeskClock) getActivity()).bringLightsUp(false);
572        }
573    }
574    private void gotoTimersView() {
575        mNewTimerPage.setVisibility(View.GONE);
576        mTimersListPage.setVisibility(View.VISIBLE);
577        startClockTicks();
578        // Use light's out setup when this fragment is within the DeskClock
579        if (getActivity() instanceof DeskClock) {
580            ((DeskClock) getActivity()).setTimerAddingTimerState(false);
581            ((DeskClock) getActivity()).bringLightsUp(false);
582        }
583    }
584
585    @Override
586    public void onClick(View v) {
587        // Use light's out setup when this fragment is within the DeskClock
588        if (getActivity() instanceof DeskClock) {
589            ((DeskClock) getActivity()).scheduleLightsOut();
590        }
591        ClickAction tag = (ClickAction) v.getTag();
592        onClickHelper(tag);
593    }
594
595    private void onClickHelper(ClickAction clickAction) {
596        switch (clickAction.mAction) {
597            case ClickAction.ACTION_DELETE:
598                TimerObj t = clickAction.mTimer;
599                if (t.mState == TimerObj.STATE_TIMESUP) {
600                    cancelTimerNotification(t.mTimerId);
601                }
602                mAdapter.deleteTimer(t.mTimerId);
603                if (mAdapter.getCount() == 0) {
604                    if (mOnEmptyListListener == null) {
605                        mTimerSetup.reset();
606                        gotoSetupView();
607                    } else {
608                        mOnEmptyListListener.onEmptyList();
609                    }
610                }
611                // Tell receiver the timer was deleted.
612                // It will stop all activity related to the timer
613                updateTimersState(t, Timers.DELETE_TIMER);
614                break;
615            case ClickAction.ACTION_PLUS_ONE:
616                onPlusOneButtonPressed(clickAction.mTimer);
617                setTimerButtons(clickAction.mTimer);
618                break;
619            case ClickAction.ACTION_STOP:
620                onStopButtonPressed(clickAction.mTimer);
621                setTimerButtons(clickAction.mTimer);
622                break;
623            default:
624                break;
625        }
626    }
627
628    private void onPlusOneButtonPressed(TimerObj t) {
629        switch(t.mState) {
630            case TimerObj.STATE_RUNNING:
631                 t.addTime(60000); //60 seconds in millis
632                 long timeLeft = t.updateTimeLeft(false);
633                 ((TimerListItem)(t.mView)).setTime(timeLeft, false);
634                 ((TimerListItem)(t.mView)).setLength(timeLeft);
635                 mAdapter.notifyDataSetChanged();
636                 updateTimersState(t, Timers.TIMER_UPDATE);
637                break;
638            case TimerObj.STATE_TIMESUP:
639                // +1 min when the time is up will restart the timer with 1 minute left.
640                t.mState = TimerObj.STATE_RUNNING;
641                t.mStartTime = Utils.getTimeNow();
642                t.mTimeLeft = t. mOriginalLength = 60000;
643                ((TimerListItem)t.mView).setTime(t.mTimeLeft, false);
644                ((TimerListItem)t.mView).set(t.mOriginalLength, t.mTimeLeft, true);
645                ((TimerListItem) t.mView).start();
646                updateTimersState(t, Timers.TIMER_RESET);
647                updateTimersState(t, Timers.START_TIMER);
648                updateTimesUpMode(t);
649                cancelTimerNotification(t.mTimerId);
650                break;
651            case TimerObj.STATE_STOPPED:
652            case TimerObj.STATE_DONE:
653                t.mState = TimerObj.STATE_RESTART;
654                t.mTimeLeft = t. mOriginalLength = t.mSetupLength;
655                ((TimerListItem)t.mView).stop();
656                ((TimerListItem)t.mView).setTime(t.mTimeLeft, false);
657                ((TimerListItem)t.mView).set(t.mOriginalLength, t.mTimeLeft, false);
658                updateTimersState(t, Timers.TIMER_RESET);
659                break;
660            default:
661                break;
662        }
663    }
664
665
666
667
668    private void onStopButtonPressed(TimerObj t) {
669        switch(t.mState) {
670            case TimerObj.STATE_RUNNING:
671                // Stop timer and save the remaining time of the timer
672                t.mState = TimerObj.STATE_STOPPED;
673                ((TimerListItem) t.mView).pause();
674                t.updateTimeLeft(true);
675                updateTimersState(t, Timers.TIMER_STOP);
676                break;
677            case TimerObj.STATE_STOPPED:
678                // Reset the remaining time and continue timer
679                t.mState = TimerObj.STATE_RUNNING;
680                t.mStartTime = Utils.getTimeNow() - (t.mOriginalLength - t.mTimeLeft);
681                ((TimerListItem) t.mView).start();
682                updateTimersState(t, Timers.START_TIMER);
683                break;
684            case TimerObj.STATE_TIMESUP:
685                t.mState = TimerObj.STATE_DONE;
686                ((TimerListItem) t.mView).done();
687                updateTimersState(t, Timers.TIMER_DONE);
688                cancelTimerNotification(t.mTimerId);
689                updateTimesUpMode(t);
690                break;
691            case TimerObj.STATE_DONE:
692                break;
693            case TimerObj.STATE_RESTART:
694                t.mState = TimerObj.STATE_RUNNING;
695                t.mStartTime = Utils.getTimeNow() - (t.mOriginalLength - t.mTimeLeft);
696                ((TimerListItem) t.mView).start();
697                updateTimersState(t, Timers.START_TIMER);
698                break;
699            default:
700                break;
701        }
702    }
703
704    private void onLabelPressed(TimerObj t) {
705        if(getActivity() instanceof DeskClock) {
706            ((DeskClock) getActivity()).removeLightsMessages();
707        }
708
709        final FragmentTransaction ft = getFragmentManager().beginTransaction();
710        final Fragment prev = getFragmentManager().findFragmentByTag("label_dialog");
711        if (prev != null) {
712            ft.remove(prev);
713        }
714        ft.addToBackStack(null);
715
716        // Create and show the dialog.
717        final LabelDialogFragment newFragment =
718                LabelDialogFragment.newInstance(t, t.mLabel, getTag());
719        newFragment.show(ft, "label_dialog");
720    }
721
722    public void setLabel(TimerObj timer, String label) {
723        ((TimerObj) mAdapter.getItem(
724                mAdapter.findTimerPositionById(timer.mTimerId))).mLabel = label;
725        if (timer.mState == TimerObj.STATE_TIMESUP) {
726            // Timer is in timesup mode.
727            TimerReceiver.showExpiredAlarmNotification(
728                    getActivity().getApplicationContext(), timer);
729        }
730    }
731
732    private void setTimerButtons(TimerObj t) {
733        Context a = getActivity();
734        if (a == null || t == null || t.mView == null) {
735            return;
736        }
737        ImageButton plusOne = (ImageButton) t.mView.findViewById(R.id.timer_plus_one);
738        CountingTimerView countingTimerView = (CountingTimerView)
739                t.mView.findViewById(R.id.timer_time_text);
740        TextView stop = (TextView) t.mView.findViewById(R.id.timer_stop);
741        Resources r = a.getResources();
742        switch (t.mState) {
743            case TimerObj.STATE_RUNNING:
744                plusOne.setVisibility(View.VISIBLE);
745                plusOne.setContentDescription(r.getString(R.string.timer_plus_one));
746                plusOne.setImageResource(R.drawable.ic_plusone);
747                stop.setContentDescription(r.getString(R.string.timer_stop));
748                stop.setText(R.string.timer_stop);
749                stop.setTextColor(getResources().getColor(R.color.clock_white));
750                countingTimerView.setVirtualButtonEnabled(true);
751                break;
752            case TimerObj.STATE_STOPPED:
753                plusOne.setVisibility(View.VISIBLE);
754                plusOne.setContentDescription(r.getString(R.string.timer_reset));
755                plusOne.setImageResource(R.drawable.ic_reset);
756                stop.setContentDescription(r.getString(R.string.timer_start));
757                stop.setText(R.string.timer_start);
758                stop.setTextColor(getResources().getColor(R.color.clock_white));
759                countingTimerView.setVirtualButtonEnabled(true);
760                break;
761            case TimerObj.STATE_TIMESUP:
762                plusOne.setVisibility(View.VISIBLE);
763                plusOne.setImageResource(R.drawable.ic_plusone);
764                stop.setContentDescription(r.getString(R.string.timer_stop));
765                stop.setTextColor(getResources().getColor(R.color.clock_white));
766                countingTimerView.setVirtualButtonEnabled(true);
767                break;
768            case TimerObj.STATE_DONE:
769                plusOne.setVisibility(View.VISIBLE);
770                plusOne.setContentDescription(r.getString(R.string.timer_reset));
771                plusOne.setImageResource(R.drawable.ic_reset);
772                stop.setVisibility(View.INVISIBLE);
773                countingTimerView.setVirtualButtonEnabled(false);
774                break;
775            case TimerObj.STATE_RESTART:
776                plusOne.setVisibility(View.INVISIBLE);
777                stop.setVisibility(View.VISIBLE);
778                stop.setContentDescription(r.getString(R.string.timer_start));
779                stop.setText(R.string.timer_start);
780                stop.setTextColor(getResources().getColor(R.color.clock_white));
781                countingTimerView.setVirtualButtonEnabled(true);
782                break;
783            default:
784                break;
785        }
786    }
787
788    private void startClockTicks() {
789        mTimersList.postDelayed(mClockTick, 20);
790        mTicking = true;
791    }
792    private void stopClockTicks() {
793        if (mTicking) {
794            mTimersList.removeCallbacks(mClockTick);
795            mTicking = false;
796        }
797    }
798
799    private void updateTimersState(TimerObj t, String action) {
800        if (!Timers.DELETE_TIMER.equals(action)) {
801            t.writeToSharedPref(mPrefs);
802        }
803        Intent i = new Intent();
804        i.setAction(action);
805        i.putExtra(Timers.TIMER_INTENT_EXTRA, t.mTimerId);
806        getActivity().sendBroadcast(i);
807    }
808
809    private void cancelTimerNotification(int timerId) {
810        mNotificationManager.cancel(timerId);
811    }
812
813    private void updateTimesUpMode(TimerObj timerObj) {
814        if (mOnEmptyListListener != null && timerObj.mState != TimerObj.STATE_TIMESUP) {
815            mAdapter.removeTimer(timerObj);
816            if (mAdapter.getCount() == 0) {
817                mOnEmptyListListener.onEmptyList();
818            }
819        }
820    }
821
822    public void restartAdapter() {
823        mAdapter = createAdapter(getActivity(), mPrefs);
824        mAdapter.onRestoreInstanceState(null);
825    }
826
827    @Override
828    public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
829        if (prefs.equals(mPrefs)) {
830            if ( (key.equals(Timers.FROM_NOTIFICATION) || key.equals(Timers.NOTIF_ID)
831                    || key.equals(Timers.NOTIF_TIME)) &&
832                    prefs.getBoolean(Timers.FROM_NOTIFICATION, false) ) {
833                // We need to know if the user has clicked the buzzing timer notification
834                // while the fragment is still open. If so, this listener will catch that event,
835                // and allow the timers to be re-instated based on the updated stop time.
836                // Because this method gets called with every change to the sharedprefs, we ensure
837                // that we only recalculate the timers if the change was specifically set by the
838                // user interacting with the notification.
839                long now = prefs.getLong(Timers.NOTIF_TIME, Utils.getTimeNow());
840                int timerId = prefs.getInt(Timers.NOTIF_ID, -1);
841                mAdapter = createAdapter(getActivity(), mPrefs);
842                mAdapter.onRestoreInstanceState(null);
843                if (timerId != -1) {
844                    TimerObj t = Timers.findTimer(mAdapter.mTimers, timerId);
845                    t.mTimeLeft = t.mOriginalLength - (now - t.mStartTime);
846                    cancelTimerNotification(timerId);
847                }
848                mTimersList.setAdapter(mAdapter);
849                SharedPreferences.Editor editor = prefs.edit();
850                editor.putBoolean(Timers.FROM_NOTIFICATION, false);
851                editor.apply();
852            }
853        }
854    }
855
856
857}
858