Java tutorial
/* * Copyright (C) 2014 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.phonemetra.deskclock.timer; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.animation.TimeInterpolator; 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.support.v4.view.ViewPager; import android.text.format.DateUtils; import android.transition.AutoTransition; import android.transition.Transition; import android.transition.TransitionManager; import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.AccelerateInterpolator; import android.view.animation.DecelerateInterpolator; import android.widget.ImageButton; import android.widget.ImageView; import com.phonemetra.deskclock.AnimatorUtils; import com.phonemetra.deskclock.DeskClock; import com.phonemetra.deskclock.DeskClockFragment; import com.phonemetra.deskclock.R; import com.phonemetra.deskclock.TimerSetupView; import com.phonemetra.deskclock.Utils; import com.phonemetra.deskclock.VerticalViewPager; public class TimerFragment extends DeskClockFragment implements OnSharedPreferenceChangeListener { public static final long ANIMATION_TIME_MILLIS = DateUtils.SECOND_IN_MILLIS / 3; private static final String KEY_SETUP_SELECTED = "_setup_selected"; private static final String KEY_ENTRY_STATE = "entry_state"; private static final int PAGINATION_DOTS_COUNT = 4; private static final String CURR_PAGE = "_currPage"; private static final TimeInterpolator ACCELERATE_INTERPOLATOR = new AccelerateInterpolator(); private static final TimeInterpolator DECELERATE_INTERPOLATOR = new DecelerateInterpolator(); private static final long ROTATE_ANIM_DURATION_MILIS = 150; private boolean mTicking = false; private TimerSetupView mSetupView; private VerticalViewPager mViewPager; private TimerFragmentAdapter mAdapter; private ImageButton mCancel; private ViewGroup mContentView; private View mTimerView; private View mLastView; private ImageView[] mPageIndicators = new ImageView[PAGINATION_DOTS_COUNT]; private Transition mDeleteTransition; private SharedPreferences mPrefs; private Bundle mViewState = null; private NotificationManager mNotificationManager; private final ViewPager.OnPageChangeListener mOnPageChangeListener = new ViewPager.SimpleOnPageChangeListener() { @Override public void onPageSelected(int position) { highlightPageIndicator(position); TimerFragment.this.setTimerViewFabIcon(getCurrentTimer()); } }; private final Runnable mClockTick = new Runnable() { boolean mVisible = true; final static int TIME_PERIOD_MS = 1000; final static int TIME_DELAY_MS = 20; final static int SPLIT = TIME_PERIOD_MS / 2; @Override public void run() { // Setup for blinking final boolean visible = Utils.getTimeNow() % TIME_PERIOD_MS < SPLIT; final boolean toggle = mVisible != visible; mVisible = visible; for (int i = 0; i < mAdapter.getCount(); i++) { final TimerObj t = mAdapter.getTimerAt(i); if (t.mState == TimerObj.STATE_RUNNING || t.mState == TimerObj.STATE_TIMESUP) { final long timeLeft = t.updateTimeLeft(false); if (t.mView != null) { t.mView.setTime(timeLeft, false); // Update button every 1/2 second if (toggle) { final ImageButton addMinuteButton = (ImageButton) t.mView.findViewById(R.id.reset_add); final boolean canAddMinute = TimerObj.MAX_TIMER_LENGTH - t.mTimeLeft > TimerObj.MINUTE_IN_MILLIS; addMinuteButton.setEnabled(canAddMinute); } } } if (t.mTimeLeft <= 0 && t.mState != TimerObj.STATE_DONE && t.mState != TimerObj.STATE_RESTART) { t.mState = TimerObj.STATE_TIMESUP; if (t.mView != null) { t.mView.timesUp(); } } // The blinking if (toggle && t.mView != null) { if (t.mState == TimerObj.STATE_TIMESUP) { t.mView.setCircleBlink(mVisible); } if (t.mState == TimerObj.STATE_STOPPED) { t.mView.setTextBlink(mVisible); } } } mTimerView.postDelayed(mClockTick, TIME_DELAY_MS); } }; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mViewState = savedInstanceState; } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { final View view = inflater.inflate(R.layout.timer_fragment, container, false); mContentView = (ViewGroup) view; mTimerView = view.findViewById(R.id.timer_view); mSetupView = (TimerSetupView) view.findViewById(R.id.timer_setup); mViewPager = (VerticalViewPager) view.findViewById(R.id.vertical_view_pager); mPageIndicators[0] = (ImageView) view.findViewById(R.id.page_indicator0); mPageIndicators[1] = (ImageView) view.findViewById(R.id.page_indicator1); mPageIndicators[2] = (ImageView) view.findViewById(R.id.page_indicator2); mPageIndicators[3] = (ImageView) view.findViewById(R.id.page_indicator3); mCancel = (ImageButton) view.findViewById(R.id.timer_cancel); mCancel.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { if (mAdapter.getCount() != 0) { final AnimatorListenerAdapter adapter = new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { mSetupView.reset(); // Make sure the setup is cleared for next time mSetupView.setScaleX(1.0f); // Reset the scale for setup view goToPagerView(); } }; createRotateAnimator(adapter, false).start(); } } }); mDeleteTransition = new AutoTransition(); mDeleteTransition.setDuration(ANIMATION_TIME_MILLIS / 2); mDeleteTransition.setInterpolator(new AccelerateDecelerateInterpolator()); return view; } @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); final Context context = getActivity(); mPrefs = PreferenceManager.getDefaultSharedPreferences(context); mNotificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); } @Override public void onResume() { super.onResume(); if (getActivity() instanceof DeskClock) { DeskClock activity = (DeskClock) getActivity(); activity.registerPageChangedListener(this); } if (mAdapter == null) { mAdapter = new TimerFragmentAdapter(getChildFragmentManager(), mPrefs); } mAdapter.populateTimersFromPref(); mViewPager.setAdapter(mAdapter); mViewPager.setOnPageChangeListener(mOnPageChangeListener); mPrefs.registerOnSharedPreferenceChangeListener(this); // Clear the flag set in the notification and alert because the adapter was just // created and is thus in sync with the database final SharedPreferences.Editor editor = mPrefs.edit(); if (mPrefs.getBoolean(Timers.FROM_NOTIFICATION, false)) { editor.putBoolean(Timers.FROM_NOTIFICATION, false); } if (mPrefs.getBoolean(Timers.FROM_ALERT, false)) { editor.putBoolean(Timers.FROM_ALERT, false); } editor.apply(); mCancel.setVisibility(mAdapter.getCount() == 0 ? View.INVISIBLE : View.VISIBLE); boolean goToSetUpView; // Process extras that were sent to the app and were intended for the timer fragment final Intent newIntent = getActivity().getIntent(); if (newIntent != null && newIntent.getBooleanExtra(TimerFullScreenFragment.GOTO_SETUP_VIEW, false)) { goToSetUpView = true; } else { if (mViewState != null) { final int currPage = mViewState.getInt(CURR_PAGE); mViewPager.setCurrentItem(currPage); highlightPageIndicator(currPage); final boolean hasPreviousInput = mViewState.getBoolean(KEY_SETUP_SELECTED, false); goToSetUpView = hasPreviousInput || mAdapter.getCount() == 0; mSetupView.restoreEntryState(mViewState, KEY_ENTRY_STATE); } else { highlightPageIndicator(0); // If user was not previously using the setup, determine which view to go by count goToSetUpView = mAdapter.getCount() == 0; } } if (goToSetUpView) { goToSetUpView(); } else { goToPagerView(); } } @Override public void onPause() { super.onPause(); if (getActivity() instanceof DeskClock) { ((DeskClock) getActivity()).unregisterPageChangedListener(this); } mPrefs.unregisterOnSharedPreferenceChangeListener(this); if (mAdapter != null) { mAdapter.saveTimersToSharedPrefs(); } stopClockTicks(); } @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); if (mAdapter != null) { mAdapter.saveTimersToSharedPrefs(); } if (mSetupView != null) { outState.putBoolean(KEY_SETUP_SELECTED, mSetupView.getVisibility() == View.VISIBLE); mSetupView.saveEntryState(outState, KEY_ENTRY_STATE); } outState.putInt(CURR_PAGE, mViewPager.getCurrentItem()); mViewState = outState; } @Override public void onDestroyView() { super.onDestroyView(); mViewState = null; } @Override public void onPageChanged(int page) { if (page == DeskClock.TIMER_TAB_INDEX && mAdapter != null) { mAdapter.notifyDataSetChanged(); } } // Starts the ticks that animate the timers. private void startClockTicks() { mTimerView.postDelayed(mClockTick, 20); mTicking = true; } // Stops the ticks that animate the timers. private void stopClockTicks() { if (mTicking) { mViewPager.removeCallbacks(mClockTick); mTicking = false; } } private void goToPagerView() { mTimerView.setVisibility(View.VISIBLE); mSetupView.setVisibility(View.GONE); mLastView = mTimerView; setLeftRightButtonAppearance(); setFabAppearance(); startClockTicks(); } private void goToSetUpView() { if (mAdapter.getCount() == 0) { mCancel.setVisibility(View.INVISIBLE); } else { mCancel.setVisibility(View.VISIBLE); } mTimerView.setVisibility(View.GONE); mSetupView.setVisibility(View.VISIBLE); mSetupView.updateDeleteButtonAndDivider(); mSetupView.registerStartButton(mFab); mLastView = mSetupView; setLeftRightButtonAppearance(); setFabAppearance(); stopClockTicks(); } private void updateTimerState(TimerObj t, String action) { if (Timers.DELETE_TIMER.equals(action)) { mAdapter.deleteTimer(t.mTimerId); if (mAdapter.getCount() == 0) { mSetupView.reset(); goToSetUpView(); } } else { t.writeToSharedPref(mPrefs); } final Intent i = new Intent(); i.setAction(action); i.putExtra(Timers.TIMER_INTENT_EXTRA, t.mTimerId); // Make sure the receiver is getting the intent ASAP. i.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); getActivity().sendBroadcast(i); } private void setTimerViewFabIcon(TimerObj timer) { final Context context = getActivity(); if (context == null || timer == null || mFab == null) { return; } final Resources r = context.getResources(); switch (timer.mState) { case TimerObj.STATE_RUNNING: mFab.setVisibility(View.VISIBLE); mFab.setContentDescription(r.getString(R.string.timer_stop)); mFab.setImageResource(R.drawable.ic_fab_pause); break; case TimerObj.STATE_STOPPED: case TimerObj.STATE_RESTART: mFab.setVisibility(View.VISIBLE); mFab.setContentDescription(r.getString(R.string.timer_start)); mFab.setImageResource(R.drawable.ic_fab_play); break; case TimerObj.STATE_DONE: // time-up then stopped mFab.setVisibility(View.INVISIBLE); break; case TimerObj.STATE_TIMESUP: // time-up but didn't stopped, continue negative ticking mFab.setVisibility(View.VISIBLE); mFab.setContentDescription(r.getString(R.string.timer_stop)); mFab.setImageResource(R.drawable.ic_fab_stop); break; default: } } private Animator getRotateFromAnimator(View view) { final Animator animator = new ObjectAnimator().ofFloat(view, View.SCALE_X, 1.0f, 0.0f); animator.setDuration(ROTATE_ANIM_DURATION_MILIS); animator.setInterpolator(DECELERATE_INTERPOLATOR); return animator; } private Animator getRotateToAnimator(View view) { final Animator animator = new ObjectAnimator().ofFloat(view, View.SCALE_X, 0.0f, 1.0f); animator.setDuration(ROTATE_ANIM_DURATION_MILIS); animator.setInterpolator(ACCELERATE_INTERPOLATOR); return animator; } private Animator getScaleFooterButtonsAnimator(final boolean show) { final AnimatorSet animatorSet = new AnimatorSet(); final Animator leftButtonAnimator = AnimatorUtils.getScaleAnimator(mLeftButton, show ? 0.0f : 1.0f, show ? 1.0f : 0.0f); final Animator rightButtonAnimator = AnimatorUtils.getScaleAnimator(mRightButton, show ? 0.0f : 1.0f, show ? 1.0f : 0.0f); final float fabStartScale = (show && mFab.getVisibility() == View.INVISIBLE) ? 0.0f : 1.0f; final Animator fabAnimator = AnimatorUtils.getScaleAnimator(mFab, fabStartScale, show ? 1.0f : 0.0f); animatorSet.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { mLeftButton.setVisibility(show ? View.VISIBLE : View.INVISIBLE); mRightButton.setVisibility(show ? View.VISIBLE : View.INVISIBLE); restoreScale(mLeftButton); restoreScale(mRightButton); restoreScale(mFab); } }); // If not show, means transiting from timer view to setup view, // when the setup view starts to rotate, the footer buttons are already invisible, // so the scaling has to finish before the setup view starts rotating animatorSet.setDuration(show ? ROTATE_ANIM_DURATION_MILIS * 2 : ROTATE_ANIM_DURATION_MILIS); animatorSet.play(leftButtonAnimator).with(rightButtonAnimator).with(fabAnimator); return animatorSet; } private void restoreScale(View view) { view.setScaleX(1.0f); view.setScaleY(1.0f); } private Animator createRotateAnimator(AnimatorListenerAdapter adapter, boolean toSetup) { final AnimatorSet animatorSet = new AnimatorSet(); final Animator rotateFrom = getRotateFromAnimator(toSetup ? mTimerView : mSetupView); rotateFrom.addListener(adapter); final Animator rotateTo = getRotateToAnimator(toSetup ? mSetupView : mTimerView); final Animator expandFooterButton = getScaleFooterButtonsAnimator(!toSetup); animatorSet.play(rotateFrom).before(rotateTo).with(expandFooterButton); return animatorSet; } @Override public void onFabClick(View view) { if (mLastView != mTimerView) { // Timer is at Setup View, so fab is "play", rotate from setup view to timer view final AnimatorListenerAdapter adapter = new AnimatorListenerAdapter() { @Override public void onAnimationStart(Animator animation) { final int timerLength = mSetupView.getTime(); final TimerObj timerObj = new TimerObj(timerLength * DateUtils.SECOND_IN_MILLIS); timerObj.mState = TimerObj.STATE_RUNNING; updateTimerState(timerObj, Timers.START_TIMER); // Go to the newly created timer view mAdapter.addTimer(timerObj); mViewPager.setCurrentItem(0); highlightPageIndicator(0); } @Override public void onAnimationEnd(Animator animation) { mSetupView.reset(); // Make sure the setup is cleared for next time mSetupView.setScaleX(1.0f); // Reset the scale for setup view goToPagerView(); } }; createRotateAnimator(adapter, false).start(); } else { // Timer is at view pager, so fab is "play" or "pause" or "square that means reset" final TimerObj t = getCurrentTimer(); switch (t.mState) { case TimerObj.STATE_RUNNING: // Stop timer and save the remaining time of the timer t.mState = TimerObj.STATE_STOPPED; t.mView.pause(); t.updateTimeLeft(true); updateTimerState(t, Timers.TIMER_STOP); break; case TimerObj.STATE_STOPPED: case TimerObj.STATE_RESTART: // Reset the remaining time and continue timer t.mState = TimerObj.STATE_RUNNING; t.mStartTime = Utils.getTimeNow() - (t.mOriginalLength - t.mTimeLeft); t.mView.start(); updateTimerState(t, Timers.START_TIMER); break; case TimerObj.STATE_TIMESUP: if (t.mDeleteAfterUse) { cancelTimerNotification(t.mTimerId); // Tell receiver the timer was deleted. // It will stop all activity related to the // timer t.mState = TimerObj.STATE_DELETED; updateTimerState(t, Timers.DELETE_TIMER); } else { t.mState = TimerObj.STATE_RESTART; t.mOriginalLength = t.mSetupLength; t.mTimeLeft = t.mSetupLength; t.mView.stop(); t.mView.setTime(t.mTimeLeft, false); t.mView.set(t.mOriginalLength, t.mTimeLeft, false); updateTimerState(t, Timers.TIMER_RESET); cancelTimerNotification(t.mTimerId); } break; } setTimerViewFabIcon(t); } } private TimerObj getCurrentTimer() { if (mViewPager == null) { return null; } final int currPage = mViewPager.getCurrentItem(); if (currPage < mAdapter.getCount()) { TimerObj o = mAdapter.getTimerAt(currPage); return o; } else { return null; } } @Override public void setFabAppearance() { final DeskClock activity = (DeskClock) getActivity(); if (mFab == null) { return; } if (activity.getSelectedTab() != DeskClock.TIMER_TAB_INDEX) { mFab.setVisibility(View.VISIBLE); return; } if (mLastView == mTimerView) { setTimerViewFabIcon(getCurrentTimer()); } else if (mSetupView != null) { mSetupView.registerStartButton(mFab); mFab.setImageResource(R.drawable.ic_fab_play); mFab.setContentDescription(getString(R.string.timer_start)); } } @Override public void setLeftRightButtonAppearance() { final DeskClock activity = (DeskClock) getActivity(); if (mLeftButton == null || mRightButton == null || activity.getSelectedTab() != DeskClock.TIMER_TAB_INDEX) { return; } mLeftButton.setEnabled(true); mRightButton.setEnabled(true); mLeftButton.setVisibility(mLastView != mTimerView ? View.GONE : View.VISIBLE); mRightButton.setVisibility(mLastView != mTimerView ? View.GONE : View.VISIBLE); mLeftButton.setImageResource(R.drawable.ic_delete); mLeftButton.setContentDescription(getString(R.string.timer_delete)); mRightButton.setImageResource(R.drawable.ic_add_timer); mRightButton.setContentDescription(getString(R.string.timer_add_timer)); } @Override public void onRightButtonClick(View view) { // Respond to add another timer final AnimatorListenerAdapter adapter = new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { mSetupView.reset(); mTimerView.setScaleX(1.0f); // Reset the scale for timer view goToSetUpView(); } }; createRotateAnimator(adapter, true).start(); } @Override public void onLeftButtonClick(View view) { // Respond to delete timer final TimerObj timer = getCurrentTimer(); if (timer == null) { return; // Prevent NPE if user click delete faster than the fade animation } if (timer.mState == TimerObj.STATE_TIMESUP) { mNotificationManager.cancel(timer.mTimerId); } if (mAdapter.getCount() == 1) { final AnimatorListenerAdapter adapter = new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { mTimerView.setScaleX(1.0f); // Reset the scale for timer view deleteTimer(timer); } }; createRotateAnimator(adapter, true).start(); } else { TransitionManager.beginDelayedTransition(mContentView, mDeleteTransition); deleteTimer(timer); } } private void deleteTimer(TimerObj timer) { // Tell receiver the timer was deleted, it will stop all activity related to the // timer timer.mState = TimerObj.STATE_DELETED; updateTimerState(timer, Timers.DELETE_TIMER); highlightPageIndicator(mViewPager.getCurrentItem()); // When deleting a negative timer (hidden fab), since deleting will not trigger // onResume(), in order to ensure the fab showing correctly, we need to manually // set fab appearance here. setFabAppearance(); } private void highlightPageIndicator(int position) { final int count = mAdapter.getCount(); if (count <= PAGINATION_DOTS_COUNT) { for (int i = 0; i < PAGINATION_DOTS_COUNT; i++) { if (count < 2 || i >= count) { mPageIndicators[i].setVisibility(View.GONE); } else { paintIndicator(i, position == i ? R.drawable.ic_swipe_circle_light : R.drawable.ic_swipe_circle_dark); } } } else { /** * If there are more than 4 timers, the top and/or bottom dot might need to show a * half fade, to indicate there are more timers in that direction. */ final int aboveCount = position; // How many timers are above the current timer final int belowCount = count - position - 1; // How many timers are below if (aboveCount < PAGINATION_DOTS_COUNT - 1) { // There's enough room for the above timers, so top dot need not to fade for (int i = 0; i < aboveCount; i++) { paintIndicator(i, R.drawable.ic_swipe_circle_dark); } paintIndicator(position, R.drawable.ic_swipe_circle_light); for (int i = position + 1; i < PAGINATION_DOTS_COUNT - 1; i++) { paintIndicator(i, R.drawable.ic_swipe_circle_dark); } paintIndicator(PAGINATION_DOTS_COUNT - 1, R.drawable.ic_swipe_circle_bottom); } else { // There's not enough room for the above timers, top dot needs to fade paintIndicator(0, R.drawable.ic_swipe_circle_top); for (int i = 1; i < PAGINATION_DOTS_COUNT - 2; i++) { paintIndicator(i, R.drawable.ic_swipe_circle_dark); } // Determine which resource to use for the "second indicator" from the bottom. paintIndicator(PAGINATION_DOTS_COUNT - 2, belowCount == 0 ? R.drawable.ic_swipe_circle_dark : R.drawable.ic_swipe_circle_light); final int lastDotRes; if (belowCount == 0) { // The current timer is the last one lastDotRes = R.drawable.ic_swipe_circle_light; } else if (belowCount == 1) { // There's only one timer below the current lastDotRes = R.drawable.ic_swipe_circle_dark; } else { // There are more than one timer below, bottom dot needs to fade lastDotRes = R.drawable.ic_swipe_circle_bottom; } paintIndicator(PAGINATION_DOTS_COUNT - 1, lastDotRes); } } } private void paintIndicator(int position, int res) { mPageIndicators[position].setVisibility(View.VISIBLE); mPageIndicators[position].setImageResource(res); } @Override public void onSharedPreferenceChanged(SharedPreferences prefs, String key) { if (prefs.equals(mPrefs)) { if ((key.equals(Timers.FROM_ALERT) && prefs.getBoolean(Timers.FROM_ALERT, false)) || (key.equals(Timers.FROM_NOTIFICATION) && prefs.getBoolean(Timers.FROM_NOTIFICATION, false))) { // The data-changed flag was set in the alert or notification so the adapter needs // to re-sync with the database SharedPreferences.Editor editor = mPrefs.edit(); editor.putBoolean(key, false); editor.apply(); mAdapter.populateTimersFromPref(); mViewPager.setAdapter(mAdapter); if (mViewState != null) { final int currPage = mViewState.getInt(CURR_PAGE); mViewPager.setCurrentItem(currPage); highlightPageIndicator(currPage); } else { highlightPageIndicator(0); } setFabAppearance(); return; } } } public void setLabel(TimerObj timer, String label) { timer.mLabel = label; updateTimerState(timer, Timers.TIMER_UPDATE); // Make sure the new label is visible. mAdapter.notifyDataSetChanged(); } public void onPlusOneButtonPressed(TimerObj t) { switch (t.mState) { case TimerObj.STATE_RUNNING: t.addTime(TimerObj.MINUTE_IN_MILLIS); long timeLeft = t.updateTimeLeft(false); t.mView.setTime(timeLeft, false); t.mView.setLength(timeLeft); mAdapter.notifyDataSetChanged(); updateTimerState(t, Timers.TIMER_UPDATE); break; case TimerObj.STATE_STOPPED: case TimerObj.STATE_DONE: t.mState = TimerObj.STATE_RESTART; t.mTimeLeft = t.mSetupLength; t.mOriginalLength = t.mSetupLength; t.mView.stop(); t.mView.setTime(t.mTimeLeft, false); t.mView.set(t.mOriginalLength, t.mTimeLeft, false); updateTimerState(t, Timers.TIMER_RESET); 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 = TimerObj.MINUTE_IN_MILLIS; t.mView.setTime(t.mTimeLeft, false); t.mView.set(t.mOriginalLength, t.mTimeLeft, true); t.mView.start(); updateTimerState(t, Timers.TIMER_RESET); updateTimerState(t, Timers.START_TIMER); cancelTimerNotification(t.mTimerId); break; } // This will change status of the timer, so update fab setFabAppearance(); } private void cancelTimerNotification(int timerId) { mNotificationManager.cancel(timerId); } }