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