blob: 8b301050b793bed54dad86eadf548c43d9e64539 [file] [log] [blame]
/*
* 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.android.deskclock.timer;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.SystemClock;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import androidx.viewpager.widget.ViewPager;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.DecelerateInterpolator;
import android.widget.Button;
import android.widget.ImageView;
import com.android.deskclock.AnimatorUtils;
import com.android.deskclock.DeskClock;
import com.android.deskclock.DeskClockFragment;
import com.android.deskclock.R;
import com.android.deskclock.Utils;
import com.android.deskclock.data.DataModel;
import com.android.deskclock.data.Timer;
import com.android.deskclock.data.TimerListener;
import com.android.deskclock.data.TimerStringFormatter;
import com.android.deskclock.events.Events;
import com.android.deskclock.uidata.UiDataModel;
import java.io.Serializable;
import java.util.Arrays;
import static android.view.View.ALPHA;
import static android.view.View.GONE;
import static android.view.View.INVISIBLE;
import static android.view.View.TRANSLATION_Y;
import static android.view.View.VISIBLE;
import static com.android.deskclock.uidata.UiDataModel.Tab.TIMERS;
/**
* Displays a vertical list of timers in all states.
*/
public final class TimerFragment extends DeskClockFragment {
private static final String EXTRA_TIMER_SETUP = "com.android.deskclock.action.TIMER_SETUP";
private static final String KEY_TIMER_SETUP_STATE = "timer_setup_input";
/** Notified when the user swipes vertically to change the visible timer. */
private final TimerPageChangeListener mTimerPageChangeListener = new TimerPageChangeListener();
/** Scheduled to update the timers while at least one is running. */
private final Runnable mTimeUpdateRunnable = new TimeUpdateRunnable();
/** Updates the {@link #mPageIndicators} in response to timers being added or removed. */
private final TimerListener mTimerWatcher = new TimerWatcher();
private TimerSetupView mCreateTimerView;
private ViewPager mViewPager;
private TimerPagerAdapter mAdapter;
private View mTimersView;
private View mCurrentView;
private ImageView[] mPageIndicators;
private Serializable mTimerSetupState;
/** {@code true} while this fragment is creating a new timer; {@code false} otherwise. */
private boolean mCreatingTimer;
/**
* @return an Intent that selects the timers tab with the setup screen for a new timer in place.
*/
public static Intent createTimerSetupIntent(Context context) {
return new Intent(context, DeskClock.class).putExtra(EXTRA_TIMER_SETUP, true);
}
/** The public no-arg constructor required by all fragments. */
public TimerFragment() {
super(TIMERS);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
final View view = inflater.inflate(R.layout.timer_fragment, container, false);
mAdapter = new TimerPagerAdapter(getFragmentManager());
mViewPager = (ViewPager) view.findViewById(R.id.vertical_view_pager);
mViewPager.setAdapter(mAdapter);
mViewPager.addOnPageChangeListener(mTimerPageChangeListener);
mTimersView = view.findViewById(R.id.timer_view);
mCreateTimerView = (TimerSetupView) view.findViewById(R.id.timer_setup);
mCreateTimerView.setFabContainer(this);
mPageIndicators = new ImageView[] {
(ImageView) view.findViewById(R.id.page_indicator0),
(ImageView) view.findViewById(R.id.page_indicator1),
(ImageView) view.findViewById(R.id.page_indicator2),
(ImageView) view.findViewById(R.id.page_indicator3)
};
DataModel.getDataModel().addTimerListener(mAdapter);
DataModel.getDataModel().addTimerListener(mTimerWatcher);
// If timer setup state is present, retrieve it to be later honored.
if (savedInstanceState != null) {
mTimerSetupState = savedInstanceState.getSerializable(KEY_TIMER_SETUP_STATE);
}
return view;
}
@Override
public void onStart() {
super.onStart();
// Initialize the page indicators.
updatePageIndicators();
boolean createTimer = false;
int showTimerId = -1;
// Examine the intent of the parent activity to determine which view to display.
final Intent intent = getActivity().getIntent();
if (intent != null) {
// These extras are single-use; remove them after honoring them.
createTimer = intent.getBooleanExtra(EXTRA_TIMER_SETUP, false);
intent.removeExtra(EXTRA_TIMER_SETUP);
showTimerId = intent.getIntExtra(TimerService.EXTRA_TIMER_ID, -1);
intent.removeExtra(TimerService.EXTRA_TIMER_ID);
}
// Choose the view to display in this fragment.
if (showTimerId != -1) {
// A specific timer must be shown; show the list of timers.
showTimersView(FAB_AND_BUTTONS_IMMEDIATE);
} else if (!hasTimers() || createTimer || mTimerSetupState != null) {
// No timers exist, a timer is being created, or the last view was timer setup;
// show the timer setup view.
showCreateTimerView(FAB_AND_BUTTONS_IMMEDIATE);
if (mTimerSetupState != null) {
mCreateTimerView.setState(mTimerSetupState);
mTimerSetupState = null;
}
} else {
// Otherwise, default to showing the list of timers.
showTimersView(FAB_AND_BUTTONS_IMMEDIATE);
}
// If the intent did not specify a timer to show, show the last timer that expired.
if (showTimerId == -1) {
final Timer timer = DataModel.getDataModel().getMostRecentExpiredTimer();
showTimerId = timer == null ? -1 : timer.getId();
}
// If a specific timer should be displayed, display the corresponding timer tab.
if (showTimerId != -1) {
final Timer timer = DataModel.getDataModel().getTimer(showTimerId);
if (timer != null) {
final int index = DataModel.getDataModel().getTimers().indexOf(timer);
mViewPager.setCurrentItem(index);
}
}
}
@Override
public void onResume() {
super.onResume();
// We may have received a new intent while paused.
final Intent intent = getActivity().getIntent();
if (intent != null && intent.hasExtra(TimerService.EXTRA_TIMER_ID)) {
// This extra is single-use; remove after honoring it.
final int showTimerId = intent.getIntExtra(TimerService.EXTRA_TIMER_ID, -1);
intent.removeExtra(TimerService.EXTRA_TIMER_ID);
final Timer timer = DataModel.getDataModel().getTimer(showTimerId);
if (timer != null) {
// A specific timer must be shown; show the list of timers.
final int index = DataModel.getDataModel().getTimers().indexOf(timer);
mViewPager.setCurrentItem(index);
animateToView(mTimersView, null, false);
}
}
}
@Override
public void onStop() {
super.onStop();
// Stop updating the timers when this fragment is no longer visible.
stopUpdatingTime();
}
@Override
public void onDestroyView() {
super.onDestroyView();
DataModel.getDataModel().removeTimerListener(mAdapter);
DataModel.getDataModel().removeTimerListener(mTimerWatcher);
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
// If the timer creation view is visible, store the input for later restoration.
if (mCurrentView == mCreateTimerView) {
mTimerSetupState = mCreateTimerView.getState();
outState.putSerializable(KEY_TIMER_SETUP_STATE, mTimerSetupState);
}
}
private void updateFab(@NonNull ImageView fab, boolean animate) {
if (mCurrentView == mTimersView) {
final Timer timer = getTimer();
if (timer == null) {
fab.setVisibility(INVISIBLE);
return;
}
fab.setVisibility(VISIBLE);
switch (timer.getState()) {
case RUNNING:
if (animate) {
fab.setImageResource(R.drawable.ic_play_pause_animation);
} else {
fab.setImageResource(R.drawable.ic_play_pause);
}
fab.setContentDescription(fab.getResources().getString(R.string.timer_stop));
break;
case RESET:
if (animate) {
fab.setImageResource(R.drawable.ic_stop_play_animation);
} else {
fab.setImageResource(R.drawable.ic_pause_play);
}
fab.setContentDescription(fab.getResources().getString(R.string.timer_start));
break;
case PAUSED:
if (animate) {
fab.setImageResource(R.drawable.ic_pause_play_animation);
} else {
fab.setImageResource(R.drawable.ic_pause_play);
}
fab.setContentDescription(fab.getResources().getString(R.string.timer_start));
break;
case MISSED:
case EXPIRED:
fab.setImageResource(R.drawable.ic_stop_white_24dp);
fab.setContentDescription(fab.getResources().getString(R.string.timer_stop));
break;
}
} else if (mCurrentView == mCreateTimerView) {
if (mCreateTimerView.hasValidInput()) {
fab.setImageResource(R.drawable.ic_start_white_24dp);
fab.setContentDescription(fab.getResources().getString(R.string.timer_start));
fab.setVisibility(VISIBLE);
} else {
fab.setContentDescription(null);
fab.setVisibility(INVISIBLE);
}
}
}
@Override
public void onUpdateFab(@NonNull ImageView fab) {
updateFab(fab, false);
}
@Override
public void onMorphFab(@NonNull ImageView fab) {
// Update the fab's drawable to match the current timer state.
updateFab(fab, Utils.isNOrLater());
// Animate the drawable.
AnimatorUtils.startDrawableAnimation(fab);
}
@Override
public void onUpdateFabButtons(@NonNull Button left, @NonNull Button right) {
if (mCurrentView == mTimersView) {
left.setClickable(true);
left.setText(R.string.timer_delete);
left.setContentDescription(left.getResources().getString(R.string.timer_delete));
left.setVisibility(VISIBLE);
right.setClickable(true);
right.setText(R.string.timer_add_timer);
right.setContentDescription(right.getResources().getString(R.string.timer_add_timer));
right.setVisibility(VISIBLE);
} else if (mCurrentView == mCreateTimerView) {
left.setClickable(true);
left.setText(R.string.timer_cancel);
left.setContentDescription(left.getResources().getString(R.string.timer_cancel));
// If no timers yet exist, the user is forced to create the first one.
left.setVisibility(hasTimers() ? VISIBLE : INVISIBLE);
right.setVisibility(INVISIBLE);
}
}
@Override
public void onFabClick(@NonNull ImageView fab) {
if (mCurrentView == mTimersView) {
final Timer timer = getTimer();
// If no timer is currently showing a fab action is meaningless.
if (timer == null) {
return;
}
final Context context = fab.getContext();
final long currentTime = timer.getRemainingTime();
switch (timer.getState()) {
case RUNNING:
DataModel.getDataModel().pauseTimer(timer);
Events.sendTimerEvent(R.string.action_stop, R.string.label_deskclock);
if (currentTime > 0) {
mTimersView.announceForAccessibility(TimerStringFormatter.formatString(
context, R.string.timer_accessibility_stopped, currentTime, true));
}
break;
case PAUSED:
case RESET:
DataModel.getDataModel().startTimer(timer);
Events.sendTimerEvent(R.string.action_start, R.string.label_deskclock);
if (currentTime > 0) {
mTimersView.announceForAccessibility(TimerStringFormatter.formatString(
context, R.string.timer_accessibility_started, currentTime, true));
}
break;
case MISSED:
case EXPIRED:
DataModel.getDataModel().resetOrDeleteTimer(timer, R.string.label_deskclock);
break;
}
} else if (mCurrentView == mCreateTimerView) {
mCreatingTimer = true;
try {
// Create the new timer.
final long timerLength = mCreateTimerView.getTimeInMillis();
final Timer timer = DataModel.getDataModel().addTimer(timerLength, "", false);
Events.sendTimerEvent(R.string.action_create, R.string.label_deskclock);
// Start the new timer.
DataModel.getDataModel().startTimer(timer);
Events.sendTimerEvent(R.string.action_start, R.string.label_deskclock);
// Display the freshly created timer view.
mViewPager.setCurrentItem(0);
} finally {
mCreatingTimer = false;
}
// Return to the list of timers.
animateToView(mTimersView, null, true);
}
}
@Override
public void onLeftButtonClick(@NonNull Button left) {
if (mCurrentView == mTimersView) {
// Clicking the "delete" button.
final Timer timer = getTimer();
if (timer == null) {
return;
}
if (mAdapter.getCount() > 1) {
animateTimerRemove(timer);
} else {
animateToView(mCreateTimerView, timer, false);
}
left.announceForAccessibility(getActivity().getString(R.string.timer_deleted));
} else if (mCurrentView == mCreateTimerView) {
// Clicking the "cancel" button on the timer creation page returns to the timers list.
mCreateTimerView.reset();
animateToView(mTimersView, null, false);
left.announceForAccessibility(getActivity().getString(R.string.timer_canceled));
}
}
@Override
public void onRightButtonClick(@NonNull Button right) {
if (mCurrentView != mCreateTimerView) {
animateToView(mCreateTimerView, null, true);
}
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (mCurrentView == mCreateTimerView) {
return mCreateTimerView.onKeyDown(keyCode, event);
}
return super.onKeyDown(keyCode, event);
}
/**
* Updates the state of the page indicators so they reflect the selected page in the context of
* all pages.
*/
private void updatePageIndicators() {
final int page = mViewPager.getCurrentItem();
final int pageIndicatorCount = mPageIndicators.length;
final int pageCount = mAdapter.getCount();
final int[] states = computePageIndicatorStates(page, pageIndicatorCount, pageCount);
for (int i = 0; i < states.length; i++) {
final int state = states[i];
final ImageView pageIndicator = mPageIndicators[i];
if (state == 0) {
pageIndicator.setVisibility(GONE);
} else {
pageIndicator.setVisibility(VISIBLE);
pageIndicator.setImageResource(state);
}
}
}
/**
* @param page the selected page; value between 0 and {@code pageCount}
* @param pageIndicatorCount the number of indicators displaying the {@code page} location
* @param pageCount the number of pages that exist
* @return an array of length {@code pageIndicatorCount} specifying which image to display for
* each page indicator or 0 if the page indicator should be hidden
*/
@VisibleForTesting
static int[] computePageIndicatorStates(int page, int pageIndicatorCount, int pageCount) {
// Compute the number of page indicators that will be visible.
final int rangeSize = Math.min(pageIndicatorCount, pageCount);
// Compute the inclusive range of pages to indicate centered around the selected page.
int rangeStart = page - (rangeSize / 2);
int rangeEnd = rangeStart + rangeSize - 1;
// Clamp the range of pages if they extend beyond the last page.
if (rangeEnd >= pageCount) {
rangeEnd = pageCount - 1;
rangeStart = rangeEnd - rangeSize + 1;
}
// Clamp the range of pages if they extend beyond the first page.
if (rangeStart < 0) {
rangeStart = 0;
rangeEnd = rangeSize - 1;
}
// Build the result with all page indicators initially hidden.
final int[] states = new int[pageIndicatorCount];
Arrays.fill(states, 0);
// If 0 or 1 total pages exist, all page indicators must remain hidden.
if (rangeSize < 2) {
return states;
}
// Initialize the visible page indicators to be dark.
Arrays.fill(states, 0, rangeSize, R.drawable.ic_swipe_circle_dark);
// If more pages exist before the first page indicator, make it a fade-in gradient.
if (rangeStart > 0) {
states[0] = R.drawable.ic_swipe_circle_top;
}
// If more pages exist after the last page indicator, make it a fade-out gradient.
if (rangeEnd < pageCount - 1) {
states[rangeSize - 1] = R.drawable.ic_swipe_circle_bottom;
}
// Set the indicator of the selected page to be light.
states[page - rangeStart] = R.drawable.ic_swipe_circle_light;
return states;
}
/**
* Display the view that creates a new timer.
*/
private void showCreateTimerView(int updateTypes) {
// Stop animating the timers.
stopUpdatingTime();
// Show the creation view; hide the timer view.
mTimersView.setVisibility(GONE);
mCreateTimerView.setVisibility(VISIBLE);
// Record the fact that the create view is visible.
mCurrentView = mCreateTimerView;
// Update the fab and buttons.
updateFab(updateTypes);
}
/**
* Display the view that lists all existing timers.
*/
private void showTimersView(int updateTypes) {
// Clear any defunct timer creation state; the next timer creation starts fresh.
mTimerSetupState = null;
// Show the timer view; hide the creation view.
mTimersView.setVisibility(VISIBLE);
mCreateTimerView.setVisibility(GONE);
// Record the fact that the create view is visible.
mCurrentView = mTimersView;
// Update the fab and buttons.
updateFab(updateTypes);
// Start animating the timers.
startUpdatingTime();
}
/**
* @param timerToRemove the timer to be removed during the animation
*/
private void animateTimerRemove(final Timer timerToRemove) {
final long duration = UiDataModel.getUiDataModel().getShortAnimationDuration();
final Animator fadeOut = ObjectAnimator.ofFloat(mViewPager, ALPHA, 1, 0);
fadeOut.setDuration(duration);
fadeOut.setInterpolator(new DecelerateInterpolator());
fadeOut.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
DataModel.getDataModel().removeTimer(timerToRemove);
Events.sendTimerEvent(R.string.action_delete, R.string.label_deskclock);
}
});
final Animator fadeIn = ObjectAnimator.ofFloat(mViewPager, ALPHA, 0, 1);
fadeIn.setDuration(duration);
fadeIn.setInterpolator(new AccelerateInterpolator());
final AnimatorSet animatorSet = new AnimatorSet();
animatorSet.play(fadeOut).before(fadeIn);
animatorSet.start();
}
/**
* @param toView one of {@link #mTimersView} or {@link #mCreateTimerView}
* @param timerToRemove the timer to be removed during the animation; {@code null} if no timer
* should be removed
* @param animateDown {@code true} if the views should animate upwards, otherwise downwards
*/
private void animateToView(final View toView, final Timer timerToRemove,
final boolean animateDown) {
if (mCurrentView == toView) {
return;
}
final boolean toTimers = toView == mTimersView;
if (toTimers) {
mTimersView.setVisibility(VISIBLE);
} else {
mCreateTimerView.setVisibility(VISIBLE);
}
// Avoid double-taps by enabling/disabling the set of buttons active on the new view.
updateFab(BUTTONS_DISABLE);
final long animationDuration = UiDataModel.getUiDataModel().getLongAnimationDuration();
final ViewTreeObserver viewTreeObserver = toView.getViewTreeObserver();
viewTreeObserver.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
if (viewTreeObserver.isAlive()) {
viewTreeObserver.removeOnPreDrawListener(this);
}
final View view = mTimersView.findViewById(R.id.timer_time);
final float distanceY = view != null ? view.getHeight() + view.getY() : 0;
final float translationDistance = animateDown ? distanceY : -distanceY;
toView.setTranslationY(-translationDistance);
mCurrentView.setTranslationY(0f);
toView.setAlpha(0f);
mCurrentView.setAlpha(1f);
final Animator translateCurrent = ObjectAnimator.ofFloat(mCurrentView,
TRANSLATION_Y, translationDistance);
final Animator translateNew = ObjectAnimator.ofFloat(toView, TRANSLATION_Y, 0f);
final AnimatorSet translationAnimatorSet = new AnimatorSet();
translationAnimatorSet.playTogether(translateCurrent, translateNew);
translationAnimatorSet.setDuration(animationDuration);
translationAnimatorSet.setInterpolator(AnimatorUtils.INTERPOLATOR_FAST_OUT_SLOW_IN);
final Animator fadeOutAnimator = ObjectAnimator.ofFloat(mCurrentView, ALPHA, 0f);
fadeOutAnimator.setDuration(animationDuration / 2);
fadeOutAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
super.onAnimationStart(animation);
// The fade-out animation and fab-shrinking animation should run together.
updateFab(FAB_AND_BUTTONS_SHRINK);
}
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
if (toTimers) {
showTimersView(FAB_AND_BUTTONS_EXPAND);
// Reset the state of the create view.
mCreateTimerView.reset();
} else {
showCreateTimerView(FAB_AND_BUTTONS_EXPAND);
}
if (timerToRemove != null) {
DataModel.getDataModel().removeTimer(timerToRemove);
Events.sendTimerEvent(R.string.action_delete, R.string.label_deskclock);
}
// Update the fab and button states now that the correct view is visible and
// before the animation to expand the fab and buttons starts.
updateFab(FAB_AND_BUTTONS_IMMEDIATE);
}
});
final Animator fadeInAnimator = ObjectAnimator.ofFloat(toView, ALPHA, 1f);
fadeInAnimator.setDuration(animationDuration / 2);
fadeInAnimator.setStartDelay(animationDuration / 2);
final AnimatorSet animatorSet = new AnimatorSet();
animatorSet.playTogether(fadeOutAnimator, fadeInAnimator, translationAnimatorSet);
animatorSet.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
mTimersView.setTranslationY(0f);
mCreateTimerView.setTranslationY(0f);
mTimersView.setAlpha(1f);
mCreateTimerView.setAlpha(1f);
}
});
animatorSet.start();
return true;
}
});
}
private boolean hasTimers() {
return mAdapter.getCount() > 0;
}
private Timer getTimer() {
if (mViewPager == null) {
return null;
}
return mAdapter.getCount() == 0 ? null : mAdapter.getTimer(mViewPager.getCurrentItem());
}
private void startUpdatingTime() {
// Ensure only one copy of the runnable is ever scheduled by first stopping updates.
stopUpdatingTime();
mViewPager.post(mTimeUpdateRunnable);
}
private void stopUpdatingTime() {
mViewPager.removeCallbacks(mTimeUpdateRunnable);
}
/**
* Periodically refreshes the state of each timer.
*/
private class TimeUpdateRunnable implements Runnable {
@Override
public void run() {
final long startTime = SystemClock.elapsedRealtime();
// If no timers require continuous updates, avoid scheduling the next update.
if (!mAdapter.updateTime()) {
return;
}
final long endTime = SystemClock.elapsedRealtime();
// Try to maintain a consistent period of time between redraws.
final long delay = Math.max(0, startTime + 20 - endTime);
mTimersView.postDelayed(this, delay);
}
}
/**
* Update the page indicators and fab in response to a new timer becoming visible.
*/
private class TimerPageChangeListener extends ViewPager.SimpleOnPageChangeListener {
@Override
public void onPageSelected(int position) {
updatePageIndicators();
updateFab(FAB_AND_BUTTONS_IMMEDIATE);
// Showing a new timer page may introduce a timer requiring continuous updates.
startUpdatingTime();
}
@Override
public void onPageScrollStateChanged(int state) {
// Teasing a neighboring timer may introduce a timer requiring continuous updates.
if (state == ViewPager.SCROLL_STATE_DRAGGING) {
startUpdatingTime();
}
}
}
/**
* Update the page indicators in response to timers being added or removed.
* Update the fab in response to the visible timer changing.
*/
private class TimerWatcher implements TimerListener {
@Override
public void timerAdded(Timer timer) {
updatePageIndicators();
// If the timer is being created via this fragment avoid adjusting the fab.
// Timer setup view is about to be animated away in response to this timer creation.
// Changes to the fab immediately preceding that animation are jarring.
if (!mCreatingTimer) {
updateFab(FAB_AND_BUTTONS_IMMEDIATE);
}
}
@Override
public void timerUpdated(Timer before, Timer after) {
// If the timer started, animate the timers.
if (before.isReset() && !after.isReset()) {
startUpdatingTime();
}
// Fetch the index of the change.
final int index = DataModel.getDataModel().getTimers().indexOf(after);
// If the timer just expired but is not displayed, display it now.
if (!before.isExpired() && after.isExpired() && index != mViewPager.getCurrentItem()) {
mViewPager.setCurrentItem(index, true);
} else if (mCurrentView == mTimersView && index == mViewPager.getCurrentItem()) {
// Morph the fab from its old state to new state if necessary.
if (before.getState() != after.getState()
&& !(before.isPaused() && after.isReset())) {
updateFab(FAB_MORPH);
}
}
}
@Override
public void timerRemoved(Timer timer) {
updatePageIndicators();
updateFab(FAB_AND_BUTTONS_IMMEDIATE);
if (mCurrentView == mTimersView && mAdapter.getCount() == 0) {
animateToView(mCreateTimerView, null, false);
}
}
}
}