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