1 /* 2 * Copyright (C) 2014 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.AnimatorSet; 22 import android.animation.ObjectAnimator; 23 import android.content.Context; 24 import android.content.Intent; 25 import android.content.res.Resources; 26 import android.os.Bundle; 27 import android.os.SystemClock; 28 import android.support.annotation.VisibleForTesting; 29 import android.support.v4.view.ViewPager; 30 import android.view.LayoutInflater; 31 import android.view.View; 32 import android.view.ViewGroup; 33 import android.view.animation.AccelerateInterpolator; 34 import android.view.animation.DecelerateInterpolator; 35 import android.widget.ImageButton; 36 import android.widget.ImageView; 37 38 import com.android.deskclock.DeskClock; 39 import com.android.deskclock.DeskClockFragment; 40 import com.android.deskclock.HandleDeskClockApiCalls; 41 import com.android.deskclock.R; 42 import com.android.deskclock.data.DataModel; 43 import com.android.deskclock.data.Timer; 44 import com.android.deskclock.data.TimerListener; 45 import com.android.deskclock.events.Events; 46 47 import java.io.Serializable; 48 import java.util.Arrays; 49 50 import static android.view.View.ALPHA; 51 import static android.view.View.GONE; 52 import static android.view.View.INVISIBLE; 53 import static android.view.View.OnClickListener; 54 import static android.view.View.SCALE_X; 55 import static android.view.View.VISIBLE; 56 import static com.android.deskclock.AnimatorUtils.getScaleAnimator; 57 58 /** 59 * Displays a vertical list of timers in all states. 60 */ 61 public class TimerFragment extends DeskClockFragment { 62 63 private static final String EXTRA_TIMER_SETUP = "com.android.deskclock.action.TIMER_SETUP"; 64 65 private static final String KEY_TIMER_SETUP_STATE = "timer_setup_input"; 66 67 /** Notified when the user swipes vertically to change the visible timer. */ 68 private final TimerPageChangeListener mTimerPageChangeListener = new TimerPageChangeListener(); 69 70 /** Scheduled to update the timers while at least one is running. */ 71 private final Runnable mTimeUpdateRunnable = new TimeUpdateRunnable(); 72 73 /** Updates the {@link #mPageIndicators} in response to timers being added or removed. */ 74 private final TimerListener mTimerWatcher = new TimerWatcher(); 75 76 private TimerSetupView mCreateTimerView; 77 private ViewPager mViewPager; 78 private TimerPagerAdapter mAdapter; 79 private ImageButton mCancelCreateButton; 80 private View mTimersView; 81 private View mCurrentView; 82 private ImageView[] mPageIndicators; 83 84 private int mShortAnimationDuration; 85 private int mMediumAnimationDuration; 86 87 private Serializable mTimerSetupState; 88 89 /** 90 * @return an Intent that selects the timers tab with the setup screen for a new timer in place. 91 */ createTimerSetupIntent(Context context)92 public static Intent createTimerSetupIntent(Context context) { 93 return new Intent(context, DeskClock.class) 94 .putExtra(DeskClock.SELECT_TAB_INTENT_EXTRA, DeskClock.TIMER_TAB_INDEX) 95 .putExtra(EXTRA_TIMER_SETUP, true); 96 } 97 98 /** The public no-arg constructor required by all fragments. */ TimerFragment()99 public TimerFragment() {} 100 101 @Override onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)102 public View onCreateView(LayoutInflater inflater, ViewGroup container, 103 Bundle savedInstanceState) { 104 final View view = inflater.inflate(R.layout.timer_fragment, container, false); 105 106 mAdapter = new TimerPagerAdapter(getChildFragmentManager()); 107 mViewPager = (ViewPager) view.findViewById(R.id.vertical_view_pager); 108 mViewPager.setAdapter(mAdapter); 109 mViewPager.addOnPageChangeListener(mTimerPageChangeListener); 110 111 mTimersView = view.findViewById(R.id.timer_view); 112 mCreateTimerView = (TimerSetupView) view.findViewById(R.id.timer_setup); 113 mPageIndicators = new ImageView[] { 114 (ImageView) view.findViewById(R.id.page_indicator0), 115 (ImageView) view.findViewById(R.id.page_indicator1), 116 (ImageView) view.findViewById(R.id.page_indicator2), 117 (ImageView) view.findViewById(R.id.page_indicator3) 118 }; 119 mCancelCreateButton = (ImageButton) view.findViewById(R.id.timer_cancel); 120 mCancelCreateButton.setOnClickListener(new CancelCreateListener()); 121 122 view.findViewById(R.id.timer_create).setOnClickListener(new CreateListener()); 123 124 final Resources resources = getResources(); 125 mShortAnimationDuration = resources.getInteger(android.R.integer.config_shortAnimTime); 126 mMediumAnimationDuration = resources.getInteger(android.R.integer.config_mediumAnimTime); 127 128 DataModel.getDataModel().addTimerListener(mAdapter); 129 DataModel.getDataModel().addTimerListener(mTimerWatcher); 130 131 // If timer setup state is present, retrieve it to be later honored. 132 if (savedInstanceState != null) { 133 mTimerSetupState = savedInstanceState.getSerializable(KEY_TIMER_SETUP_STATE); 134 } 135 136 return view; 137 } 138 139 @Override onResume()140 public void onResume() { 141 super.onResume(); 142 143 // Start watching for page changes away from this fragment. 144 getDeskClock().registerPageChangedListener(this); 145 146 // Initialize the page indicators. 147 updatePageIndicators(); 148 149 boolean createTimer = false; 150 int showTimerId = -1; 151 152 // Examine the intent of the parent activity to determine which view to display. 153 final Intent intent = getActivity().getIntent(); 154 if (intent != null) { 155 // These extras are single-use; remove them after honoring them. 156 createTimer = intent.getBooleanExtra(EXTRA_TIMER_SETUP, false); 157 intent.removeExtra(EXTRA_TIMER_SETUP); 158 159 showTimerId = intent.getIntExtra(HandleDeskClockApiCalls.EXTRA_TIMER_ID, -1); 160 intent.removeExtra(HandleDeskClockApiCalls.EXTRA_TIMER_ID); 161 } 162 163 // Choose the view to display in this fragment. 164 if (showTimerId != -1) { 165 // A specific timer must be shown; show the list of timers. 166 showTimersView(); 167 } else if (!hasTimers() || createTimer || mTimerSetupState != null) { 168 // No timers exist, a timer is being created, or the last view was timer setup; 169 // show the timer setup view. 170 showCreateTimerView(); 171 172 if (mTimerSetupState != null) { 173 mCreateTimerView.setState(mTimerSetupState); 174 mTimerSetupState = null; 175 } 176 } else { 177 // Otherwise, default to showing the list of timers. 178 showTimersView(); 179 } 180 181 // If the intent did not specify a timer to show, show the last timer that expired. 182 if (showTimerId == -1) { 183 final Timer timer = DataModel.getDataModel().getMostRecentExpiredTimer(); 184 showTimerId = timer == null ? -1 : timer.getId(); 185 } 186 187 // If a specific timer should be displayed, display the corresponding timer tab. 188 if (showTimerId != -1) { 189 final Timer timer = DataModel.getDataModel().getTimer(showTimerId); 190 if (timer != null) { 191 final int index = DataModel.getDataModel().getTimers().indexOf(timer); 192 mViewPager.setCurrentItem(index); 193 } 194 } 195 } 196 197 @Override onPause()198 public void onPause() { 199 super.onPause(); 200 201 // Stop watching for page changes away from this fragment. 202 getDeskClock().unregisterPageChangedListener(this); 203 } 204 205 @Override onStop()206 public void onStop() { 207 super.onStop(); 208 209 // Stop updating the timers when this fragment is no longer visible. 210 stopUpdatingTime(); 211 } 212 213 @Override onDestroyView()214 public void onDestroyView() { 215 super.onDestroyView(); 216 217 DataModel.getDataModel().removeTimerListener(mAdapter); 218 DataModel.getDataModel().removeTimerListener(mTimerWatcher); 219 } 220 221 @Override onSaveInstanceState(Bundle outState)222 public void onSaveInstanceState(Bundle outState) { 223 super.onSaveInstanceState(outState); 224 225 // If the timer creation view is visible, store the input for later restoration. 226 if (mCurrentView == mCreateTimerView) { 227 mTimerSetupState = mCreateTimerView.getState(); 228 outState.putSerializable(KEY_TIMER_SETUP_STATE, mTimerSetupState); 229 } 230 } 231 232 @Override setFabAppearance()233 public void setFabAppearance() { 234 if (mFab == null || getSelectedTab() != DeskClock.TIMER_TAB_INDEX) { 235 return; 236 } 237 238 if (mCurrentView == mTimersView) { 239 final Timer timer = getTimer(); 240 if (timer == null) { 241 mFab.setVisibility(INVISIBLE); 242 return; 243 } 244 245 mFab.setVisibility(VISIBLE); 246 switch (timer.getState()) { 247 case RUNNING: 248 mFab.setImageResource(R.drawable.ic_pause_white_24dp); 249 mFab.setContentDescription(getString(R.string.timer_stop)); 250 break; 251 case RESET: 252 case PAUSED: 253 mFab.setImageResource(R.drawable.ic_start_white_24dp); 254 mFab.setContentDescription(getString(R.string.timer_start)); 255 break; 256 case EXPIRED: 257 mFab.setImageResource(R.drawable.ic_stop_white_24dp); 258 mFab.setContentDescription(getString(R.string.timer_stop)); 259 break; 260 } 261 262 } else if (mCurrentView == mCreateTimerView) { 263 mFab.setVisibility(INVISIBLE); 264 } 265 } 266 267 @Override onFabClick(View view)268 public void onFabClick(View view) { 269 final Timer timer = getTimer(); 270 271 // If no timer is currently showing a fab action is meaningless. 272 if (timer == null) { 273 return; 274 } 275 276 switch (timer.getState()) { 277 case RUNNING: 278 DataModel.getDataModel().pauseTimer(timer); 279 Events.sendTimerEvent(R.string.action_stop, R.string.label_deskclock); 280 break; 281 case PAUSED: 282 case RESET: 283 DataModel.getDataModel().startTimer(timer); 284 Events.sendTimerEvent(R.string.action_start, R.string.label_deskclock); 285 break; 286 case EXPIRED: 287 DataModel.getDataModel().resetOrDeleteTimer(timer, R.string.label_deskclock); 288 break; 289 } 290 } 291 292 @Override setLeftRightButtonAppearance()293 public void setLeftRightButtonAppearance() { 294 if (mLeftButton == null || mRightButton == null || 295 getSelectedTab() != DeskClock.TIMER_TAB_INDEX) { 296 return; 297 } 298 299 mLeftButton.setEnabled(true); 300 mLeftButton.setImageResource(R.drawable.ic_delete); 301 mLeftButton.setContentDescription(getString(R.string.timer_delete)); 302 mLeftButton.setVisibility(mCurrentView != mTimersView ? GONE : VISIBLE); 303 304 mRightButton.setEnabled(true); 305 mRightButton.setImageResource(R.drawable.ic_add_timer); 306 mRightButton.setContentDescription(getString(R.string.timer_add_timer)); 307 mRightButton.setVisibility(mCurrentView != mTimersView ? GONE : VISIBLE); 308 } 309 310 @Override onLeftButtonClick(View view)311 public void onLeftButtonClick(View view) { 312 final Timer timer = getTimer(); 313 if (timer == null) { 314 return; 315 } 316 317 if (mAdapter.getCount() > 1) { 318 animateTimerRemove(timer); 319 } else { 320 animateToView(mCreateTimerView, timer); 321 } 322 323 view.announceForAccessibility(getActivity().getString(R.string.timer_deleted)); 324 } 325 326 @Override onRightButtonClick(View view)327 public void onRightButtonClick(View view) { 328 animateToView(mCreateTimerView, null); 329 } 330 331 /** 332 * Updates the state of the page indicators so they reflect the selected page in the context of 333 * all pages. 334 */ updatePageIndicators()335 private void updatePageIndicators() { 336 final int page = mViewPager.getCurrentItem(); 337 final int pageIndicatorCount = mPageIndicators.length; 338 final int pageCount = mAdapter.getCount(); 339 340 final int[] states = computePageIndicatorStates(page, pageIndicatorCount, pageCount); 341 for (int i = 0; i < states.length; i++) { 342 final int state = states[i]; 343 final ImageView pageIndicator = mPageIndicators[i]; 344 if (state == 0) { 345 pageIndicator.setVisibility(GONE); 346 } else { 347 pageIndicator.setVisibility(VISIBLE); 348 pageIndicator.setImageResource(state); 349 } 350 } 351 } 352 353 /** 354 * @param page the selected page; value between 0 and {@code pageCount} 355 * @param pageIndicatorCount the number of indicators displaying the {@code page} location 356 * @param pageCount the number of pages that exist 357 * @return an array of length {@code pageIndicatorCount} specifying which image to display for 358 * each page indicator or 0 if the page indicator should be hidden 359 */ 360 @VisibleForTesting computePageIndicatorStates(int page, int pageIndicatorCount, int pageCount)361 static int[] computePageIndicatorStates(int page, int pageIndicatorCount, int pageCount) { 362 // Compute the number of page indicators that will be visible. 363 final int rangeSize = Math.min(pageIndicatorCount, pageCount); 364 365 // Compute the inclusive range of pages to indicate centered around the selected page. 366 int rangeStart = page - (rangeSize / 2); 367 int rangeEnd = rangeStart + rangeSize - 1; 368 369 // Clamp the range of pages if they extend beyond the last page. 370 if (rangeEnd >= pageCount) { 371 rangeEnd = pageCount - 1; 372 rangeStart = rangeEnd - rangeSize + 1; 373 } 374 375 // Clamp the range of pages if they extend beyond the first page. 376 if (rangeStart < 0) { 377 rangeStart = 0; 378 rangeEnd = rangeSize - 1; 379 } 380 381 // Build the result with all page indicators initially hidden. 382 final int[] states = new int[pageIndicatorCount]; 383 Arrays.fill(states, 0); 384 385 // If 0 or 1 total pages exist, all page indicators must remain hidden. 386 if (rangeSize < 2) { 387 return states; 388 } 389 390 // Initialize the visible page indicators to be dark. 391 Arrays.fill(states, 0, rangeSize, R.drawable.ic_swipe_circle_dark); 392 393 // If more pages exist before the first page indicator, make it a fade-in gradient. 394 if (rangeStart > 0) { 395 states[0] = R.drawable.ic_swipe_circle_top; 396 } 397 398 // If more pages exist after the last page indicator, make it a fade-out gradient. 399 if (rangeEnd < pageCount - 1) { 400 states[rangeSize - 1] = R.drawable.ic_swipe_circle_bottom; 401 } 402 403 // Set the indicator of the selected page to be light. 404 states[page - rangeStart] = R.drawable.ic_swipe_circle_light; 405 406 return states; 407 } 408 409 /** 410 * Display the view that creates a new timer. 411 */ showCreateTimerView()412 private void showCreateTimerView() { 413 // Stop animating the timers. 414 stopUpdatingTime(); 415 416 // If no timers yet exist, the user is forced to create the first one. 417 mCancelCreateButton.setVisibility(hasTimers() ? VISIBLE : INVISIBLE); 418 mCancelCreateButton.setEnabled(true); 419 420 // Show the creation view; hide the timer view. 421 mTimersView.setVisibility(GONE); 422 mCreateTimerView.setVisibility(VISIBLE); 423 424 // Record the fact that the create view is visible. 425 mCurrentView = mCreateTimerView; 426 427 // Update the fab and buttons. 428 setLeftRightButtonAppearance(); 429 setFabAppearance(); 430 } 431 432 /** 433 * Display the view that lists all existing timers. 434 */ showTimersView()435 private void showTimersView() { 436 // Show the timer view; hide the creation view. 437 mTimersView.setVisibility(VISIBLE); 438 mCreateTimerView.setVisibility(GONE); 439 440 // Record the fact that the create view is visible. 441 mCurrentView = mTimersView; 442 443 // Update the fab and buttons. 444 setLeftRightButtonAppearance(); 445 setFabAppearance(); 446 447 // Start animating the timers. 448 startUpdatingTime(); 449 } 450 451 /** 452 * @param timerToRemove the timer to be removed during the animation 453 */ animateTimerRemove(final Timer timerToRemove)454 private void animateTimerRemove(final Timer timerToRemove) { 455 final Animator fadeOut = ObjectAnimator.ofFloat(mViewPager, ALPHA, 1, 0); 456 fadeOut.setDuration(mShortAnimationDuration); 457 fadeOut.setInterpolator(new DecelerateInterpolator()); 458 fadeOut.addListener(new AnimatorListenerAdapter() { 459 @Override 460 public void onAnimationEnd(Animator animation) { 461 DataModel.getDataModel().removeTimer(timerToRemove); 462 Events.sendTimerEvent(R.string.action_delete, R.string.label_deskclock); 463 } 464 }); 465 466 final Animator fadeIn = ObjectAnimator.ofFloat(mViewPager, ALPHA, 0, 1); 467 fadeIn.setDuration(mShortAnimationDuration); 468 fadeIn.setInterpolator(new AccelerateInterpolator()); 469 470 final AnimatorSet animatorSet = new AnimatorSet(); 471 animatorSet.play(fadeOut).before(fadeIn); 472 animatorSet.start(); 473 } 474 475 /** 476 * @param toView one of {@link #mTimersView} or {@link #mCreateTimerView} 477 * @param timerToRemove the timer to be removed during the animation; {@code null} if no timer 478 * should be removed 479 */ animateToView(View toView, final Timer timerToRemove)480 private void animateToView(View toView, final Timer timerToRemove) { 481 if (mCurrentView == toView) { 482 throw new IllegalStateException("toView is already the current view"); 483 } 484 485 final boolean toTimers = toView == mTimersView; 486 487 // Avoid double-taps by enabling/disabling the set of buttons active on the new view. 488 mLeftButton.setEnabled(toTimers); 489 mRightButton.setEnabled(toTimers); 490 mCancelCreateButton.setEnabled(!toTimers); 491 492 final Animator rotateFrom = ObjectAnimator.ofFloat(mCurrentView, SCALE_X, 1, 0); 493 rotateFrom.setDuration(mShortAnimationDuration); 494 rotateFrom.setInterpolator(new DecelerateInterpolator()); 495 rotateFrom.addListener(new AnimatorListenerAdapter() { 496 @Override 497 public void onAnimationEnd(Animator animation) { 498 if (timerToRemove != null) { 499 DataModel.getDataModel().removeTimer(timerToRemove); 500 Events.sendTimerEvent(R.string.action_delete, R.string.label_deskclock); 501 } 502 503 mCurrentView.setScaleX(1); 504 if (toTimers) { 505 showTimersView(); 506 } else { 507 showCreateTimerView(); 508 } 509 } 510 }); 511 512 final Animator rotateTo = ObjectAnimator.ofFloat(toView, SCALE_X, 0, 1); 513 rotateTo.setDuration(mShortAnimationDuration); 514 rotateTo.setInterpolator(new AccelerateInterpolator()); 515 516 final float preScale = toTimers ? 0 : 1; 517 final float postScale = toTimers ? 1 : 0; 518 final Animator fabAnimator = getScaleAnimator(mFab, preScale, postScale); 519 final Animator leftButtonAnimator = getScaleAnimator(mLeftButton, preScale, postScale); 520 final Animator rightButtonAnimator = getScaleAnimator(mRightButton, preScale, postScale); 521 522 final AnimatorSet buttons = new AnimatorSet(); 523 buttons.setDuration(toTimers ? mMediumAnimationDuration : mShortAnimationDuration); 524 buttons.play(leftButtonAnimator).with(rightButtonAnimator).with(fabAnimator); 525 buttons.addListener(new AnimatorListenerAdapter() { 526 @Override 527 public void onAnimationEnd(Animator animation) { 528 mLeftButton.setVisibility(toTimers ? VISIBLE : INVISIBLE); 529 mRightButton.setVisibility(toTimers ? VISIBLE : INVISIBLE); 530 531 mFab.setScaleX(1); 532 mFab.setScaleY(1); 533 mLeftButton.setScaleX(1); 534 mLeftButton.setScaleY(1); 535 mRightButton.setScaleX(1); 536 mRightButton.setScaleY(1); 537 } 538 }); 539 540 final AnimatorSet animatorSet = new AnimatorSet(); 541 animatorSet.play(rotateFrom).before(rotateTo).with(buttons); 542 animatorSet.start(); 543 } 544 hasTimers()545 private boolean hasTimers() { 546 return mAdapter.getCount() > 0; 547 } 548 getTimer()549 private Timer getTimer() { 550 if (mViewPager == null) { 551 return null; 552 } 553 554 return mAdapter.getCount() == 0 ? null : mAdapter.getTimer(mViewPager.getCurrentItem()); 555 } 556 startUpdatingTime()557 private void startUpdatingTime() { 558 // Ensure only one copy of the runnable is ever scheduled by first stopping updates. 559 stopUpdatingTime(); 560 mViewPager.post(mTimeUpdateRunnable); 561 } 562 stopUpdatingTime()563 private void stopUpdatingTime() { 564 mViewPager.removeCallbacks(mTimeUpdateRunnable); 565 } 566 567 /** 568 * Periodically refreshes the state of each timer. 569 */ 570 private class TimeUpdateRunnable implements Runnable { 571 @Override run()572 public void run() { 573 final long startTime = SystemClock.elapsedRealtime(); 574 // If no timers require continuous updates, avoid scheduling the next update. 575 if (!mAdapter.updateTime()) { 576 return; 577 } 578 final long endTime = SystemClock.elapsedRealtime(); 579 580 // Try to maintain a consistent period of time between redraws. 581 final long delay = Math.max(0, startTime + 20 - endTime); 582 mTimersView.postDelayed(this, delay); 583 } 584 } 585 586 /** 587 * Update the page indicators and fab in response to a new timer becoming visible. 588 */ 589 private class TimerPageChangeListener extends ViewPager.SimpleOnPageChangeListener { 590 @Override onPageSelected(int position)591 public void onPageSelected(int position) { 592 updatePageIndicators(); 593 setFabAppearance(); 594 595 // Showing a new timer page may introduce a timer requiring continuous updates. 596 startUpdatingTime(); 597 } 598 599 @Override onPageScrollStateChanged(int state)600 public void onPageScrollStateChanged(int state) { 601 // Teasing a neighboring timer may introduce a timer requiring continuous updates. 602 if (state == ViewPager.SCROLL_STATE_DRAGGING) { 603 startUpdatingTime(); 604 } 605 } 606 } 607 608 /** 609 * Update the page indicators in response to timers being added or removed. 610 * Update the fab in response to the visible timer changing. 611 */ 612 private class TimerWatcher implements TimerListener { 613 @Override timerAdded(Timer timer)614 public void timerAdded(Timer timer) { 615 // The user interface should not be updated unless the fragment is resumed. It will be 616 // refreshed during onResume later if it is not currently resumed. 617 if (!isResumed()) { 618 return; 619 } 620 621 updatePageIndicators(); 622 } 623 624 @Override timerUpdated(Timer before, Timer after)625 public void timerUpdated(Timer before, Timer after) { 626 // The user interface should not be updated unless the fragment is resumed. It will be 627 // refreshed during onResume later if it is not currently resumed. 628 if (!isResumed()) { 629 return; 630 } 631 632 // If the timer started, animate the timers. 633 if (before.isReset() && !after.isReset()) { 634 startUpdatingTime(); 635 } 636 637 // Fetch the index of the change. 638 final int index = DataModel.getDataModel().getTimers().indexOf(after); 639 640 // If the timer just expired but is not displayed, display it now. 641 if (!before.isExpired() && after.isExpired() && index != mViewPager.getCurrentItem()) { 642 mViewPager.setCurrentItem(index, true); 643 644 } else if (index == mViewPager.getCurrentItem()) { 645 // If the visible timer changed, update the fab to match its new state. 646 setFabAppearance(); 647 } 648 } 649 650 @Override timerRemoved(Timer timer)651 public void timerRemoved(Timer timer) { 652 // The user interface should not be updated unless the fragment is resumed. It will be 653 // refreshed during onResume later if it is not currently resumed. 654 if (!isResumed()) { 655 return; 656 } 657 658 updatePageIndicators(); 659 } 660 } 661 662 /** 663 * Clicking the play icon on the timer creation page creates a new timer and returns to the 664 * timers list. 665 */ 666 private class CreateListener implements OnClickListener { 667 @Override onClick(View v)668 public void onClick(View v) { 669 // Create the new timer. 670 final long length = mCreateTimerView.getTimeInMillis(); 671 final Timer timer = DataModel.getDataModel().addTimer(length, "", false); 672 Events.sendTimerEvent(R.string.action_create, R.string.label_deskclock); 673 674 // Start the new timer. 675 DataModel.getDataModel().startTimer(timer); 676 Events.sendTimerEvent(R.string.action_start, R.string.label_deskclock); 677 678 // Reset the state of the create view. 679 mCreateTimerView.reset(); 680 681 // Display the freshly created timer view. 682 mViewPager.setCurrentItem(0); 683 684 // Return to the list of timers. 685 animateToView(mTimersView, null); 686 } 687 } 688 689 /** 690 * Clicking the X icon on the timer creation page returns to the timers list. 691 */ 692 private class CancelCreateListener implements OnClickListener { 693 @Override onClick(View view)694 public void onClick(View view) { 695 // Reset the state of the create view. 696 mCreateTimerView.reset(); 697 698 if (hasTimers()) { 699 animateToView(mTimersView, null); 700 } 701 702 view.announceForAccessibility(getActivity().getString(R.string.timer_canceled)); 703 } 704 } 705 }