TimerFragment.java revision 22ad7c9ea127b06ee0dc0cb809b59bab33afbf42
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.NotificationManager; 20import android.content.Context; 21import android.content.Intent; 22import android.content.SharedPreferences; 23import android.content.SharedPreferences.OnSharedPreferenceChangeListener; 24import android.content.res.Resources; 25import android.os.Bundle; 26import android.preference.PreferenceManager; 27import android.util.Log; 28import android.view.LayoutInflater; 29import android.view.MotionEvent; 30import android.view.View; 31import android.view.View.OnClickListener; 32import android.view.View.OnTouchListener; 33import android.view.ViewGroup; 34import android.widget.BaseAdapter; 35import android.widget.Button; 36import android.widget.ImageButton; 37import android.widget.ListView; 38 39import com.android.deskclock.DeskClockFragment; 40import com.android.deskclock.R; 41import com.android.deskclock.TimerSetupView; 42import com.android.deskclock.Utils; 43 44import java.util.ArrayList; 45import java.util.Collections; 46import java.util.Comparator; 47 48 49public class TimerFragment extends DeskClockFragment 50 implements OnClickListener, OnSharedPreferenceChangeListener { 51 52 private static final String TAG = "TimerFragment"; 53 private ListView mTimersList; 54 private View mNewTimerPage; 55 private View mTimersListPage; 56 private Button mCancel, mStart; 57 private ImageButton mAddTimer; 58 private TimerSetupView mTimerSetup; 59 private TimersListAdapter mAdapter; 60 private boolean mTicking = false; 61 private SharedPreferences mPrefs; 62 private NotificationManager mNotificationManager; 63 private OnEmptyListListener mOnEmptyListListener; 64 65 public TimerFragment() { 66 } 67 68 class ClickAction { 69 public static final int ACTION_STOP = 1; 70 public static final int ACTION_PLUS_ONE = 2; 71 public static final int ACTION_DELETE = 3; 72 73 public int mAction; 74 public TimerObj mTimer; 75 76 public ClickAction(int action, TimerObj t) { 77 mAction = action; 78 mTimer = t; 79 } 80 } 81 82 // Container Activity that requests TIMESUP_MODE must implement this interface 83 public interface OnEmptyListListener { 84 public void onEmptyList(); 85 } 86 87 TimersListAdapter createAdapter(Context context, SharedPreferences prefs) { 88 if (mOnEmptyListListener == null) { 89 return new TimersListAdapter(context, prefs); 90 } else { 91 return new TimesUpListAdapter(context, prefs); 92 } 93 } 94 95 class TimersListAdapter extends BaseAdapter { 96 97 ArrayList<TimerObj> mTimers = new ArrayList<TimerObj> (); 98 private final LayoutInflater mInflater; 99 Context mContext; 100 SharedPreferences mmPrefs; 101 102 public TimersListAdapter(Context context, SharedPreferences prefs) { 103 mContext = context; 104 mInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 105 mmPrefs = prefs; 106 } 107 108 @Override 109 public int getCount() { 110 return mTimers.size(); 111 } 112 113 @Override 114 public Object getItem(int p) { 115 return mTimers.get(p); 116 } 117 118 @Override 119 public long getItemId(int p) { 120 if (p >= 0 && p < mTimers.size()) { 121 return mTimers.get(p).mTimerId; 122 } 123 return 0; 124 } 125 126 public void deleteTimer(int id) { 127 for (int i = 0; i < mTimers.size(); i++) { 128 TimerObj t = mTimers.get(i); 129 if (t.mTimerId == id) { 130 ((TimerListItem)t.mView).stop(); 131 t.deleteFromSharedPref(mmPrefs); 132 mTimers.remove(i); 133 notifyDataSetChanged(); 134 return; 135 } 136 } 137 } 138 139 protected int findTimerPositionById(int id) { 140 for (int i = 0; i < mTimers.size(); i++) { 141 TimerObj t = mTimers.get(i); 142 if (t.mTimerId == id) { 143 return i; 144 } 145 } 146 return -1; 147 } 148 149 public void removeTimer(TimerObj timerObj) { 150 int position = findTimerPositionById(timerObj.mTimerId); 151 if (position >= 0) { 152 mTimers.remove(position); 153 notifyDataSetChanged(); 154 } 155 } 156 157 @Override 158 public View getView(int position, View convertView, ViewGroup parent) { 159 TimerListItem v; 160 161 // if (convertView != null) { 162 // v = (TimerListItem) convertView; 163 // } else { 164 v = new TimerListItem (mContext); 165 // } 166 167 final TimerObj o = (TimerObj)getItem(position); 168 o.mView = v; 169 long timeLeft = o.updateTimeLeft(false); 170 boolean drawRed = o.mState != TimerObj.STATE_RESTART; 171 v.set(o.mOriginalLength, timeLeft, drawRed); 172 v.setTime(timeLeft, true); 173 switch (o.mState) { 174 case TimerObj.STATE_RUNNING: 175 v.start(); 176 break; 177 case TimerObj.STATE_TIMESUP: 178 v.timesUp(); 179 break; 180 case TimerObj.STATE_DONE: 181 v.done(); 182 break; 183 default: 184 break; 185 } 186 187 // Timer text serves as a virtual start/stop button. 188 final CountingTimerView countingTimerView = (CountingTimerView) 189 v.findViewById(R.id.timer_time_text); 190 countingTimerView.registerVirtualButtonAction(new Runnable() { 191 @Override 192 public void run() { 193 TimerFragment.this.onClickHelper( 194 new ClickAction(ClickAction.ACTION_STOP, o)); 195 } 196 }); 197 198 ImageButton delete = (ImageButton)v.findViewById(R.id.timer_delete); 199 delete.setOnClickListener(TimerFragment.this); 200 delete.setTag(new ClickAction(ClickAction.ACTION_DELETE, o)); 201 ImageButton plusOne = (ImageButton)v. findViewById(R.id.timer_plus_one); 202 plusOne.setOnClickListener(TimerFragment.this); 203 plusOne.setTag(new ClickAction(ClickAction.ACTION_PLUS_ONE, o)); 204 ImageButton stop = (ImageButton)v. findViewById(R.id.timer_stop); 205 stop.setOnClickListener(TimerFragment.this); 206 stop.setTag(new ClickAction(ClickAction.ACTION_STOP, o)); 207 TimerFragment.this.setTimerButtons(o); 208 return v; 209 } 210 211 public void addTimer(TimerObj t) { 212 mTimers.add(0, t); 213 notifyDataSetChanged(); 214 } 215 216 public void onSaveInstanceState(Bundle outState) { 217 TimerObj.putTimersInSharedPrefs(mmPrefs, mTimers); 218 } 219 220 public void onRestoreInstanceState(Bundle outState) { 221 TimerObj.getTimersFromSharedPrefs(mmPrefs, mTimers); 222 notifyDataSetChanged(); 223 } 224 225 public void saveGlobalState() { 226 TimerObj.putTimersInSharedPrefs(mmPrefs, mTimers); 227 } 228 } 229 230 class TimesUpListAdapter extends TimersListAdapter { 231 232 public TimesUpListAdapter(Context context, SharedPreferences prefs) { 233 super(context, prefs); 234 } 235 236 @Override 237 public void onSaveInstanceState(Bundle outState) { 238 // This adapter has a data subset and never updates entire database 239 // Individual timers are updated in button handlers. 240 } 241 242 @Override 243 public void saveGlobalState() { 244 // This adapter has a data subset and never updates entire database 245 // Individual timers are updated in button handlers. 246 } 247 248 @Override 249 public void onRestoreInstanceState(Bundle outState) { 250 // This adapter loads a subset 251 TimerObj.getTimersFromSharedPrefs(mmPrefs, mTimers, TimerObj.STATE_TIMESUP); 252 253 if (getCount() == 0) { 254 mOnEmptyListListener.onEmptyList(); 255 } else { 256 Collections.sort(mTimers, new Comparator<TimerObj>() { 257 @Override 258 public int compare(TimerObj o1, TimerObj o2) { 259 return (int)(o1.mTimeLeft - o2.mTimeLeft); 260 } 261 }); 262 } 263 } 264 } 265 266 private final Runnable mClockTick = new Runnable() { 267 boolean mVisible = true; 268 final static int TIME_PERIOD_MS = 1000; 269 final static int SPLIT = TIME_PERIOD_MS / 2; 270 271 @Override 272 public void run() { 273 // Setup for blinking 274 boolean visible = Utils.getTimeNow() % TIME_PERIOD_MS < SPLIT; 275 boolean toggle = mVisible != visible; 276 mVisible = visible; 277 for (int i = 0; i < mAdapter.getCount(); i ++) { 278 TimerObj t = (TimerObj) mAdapter.getItem(i); 279 if (t.mState == TimerObj.STATE_RUNNING || t.mState == TimerObj.STATE_TIMESUP) { 280 long timeLeft = t.updateTimeLeft(false); 281 if ((TimerListItem)(t.mView) != null) { 282 ((TimerListItem)(t.mView)).setTime(timeLeft, false); 283 } 284 } 285 if (t.mTimeLeft <= 0 && t.mState != TimerObj.STATE_DONE 286 && t.mState != TimerObj.STATE_RESTART) { 287 t.mState = TimerObj.STATE_TIMESUP; 288 TimerFragment.this.setTimerButtons(t); 289 if ((TimerListItem)(t.mView) != null) { 290 ((TimerListItem)(t.mView)).timesUp(); 291 } 292 } 293 294 // The blinking 295 if (toggle && (TimerListItem)(t.mView) != null) { 296 if (t.mState == TimerObj.STATE_TIMESUP) { 297 ((TimerListItem)(t.mView)).setCircleBlink(mVisible); 298 } 299 if (t.mState == TimerObj.STATE_STOPPED) { 300 ((TimerListItem)(t.mView)).setTextBlink(mVisible); 301 } 302 } 303 } 304 mTimersList.postDelayed(mClockTick, 20); 305 } 306 }; 307 308 @Override 309 public View onCreateView(LayoutInflater inflater, ViewGroup container, 310 Bundle savedInstanceState) { 311 // Inflate the layout for this fragment 312 View v = inflater.inflate(R.layout.timer_fragment, container, false); 313 314 // Handle arguments from parent 315 Bundle bundle = getArguments(); 316 if (bundle != null && bundle.containsKey(Timers.TIMESUP_MODE)) { 317 if (bundle.getBoolean(Timers.TIMESUP_MODE, false)) { 318 try { 319 mOnEmptyListListener = (OnEmptyListListener) getActivity(); 320 } catch (ClassCastException e) { 321 Log.wtf(TAG, getActivity().toString() + " must implement OnEmptyListListener"); 322 } 323 } 324 } 325 326 mTimersList = (ListView)v.findViewById(R.id.timers_list); 327 mNewTimerPage = v.findViewById(R.id.new_timer_page); 328 mTimersListPage = v.findViewById(R.id.timers_list_page); 329 mTimerSetup = (TimerSetupView)v.findViewById(R.id.timer_setup); 330 mCancel = (Button)v.findViewById(R.id.timer_cancel); 331 mCancel.setOnClickListener(new OnClickListener() { 332 @Override 333 public void onClick(View v) { 334 if (mAdapter.getCount() != 0) { 335 gotoTimersView(); 336 } 337 } 338 }); 339 mStart = (Button)v.findViewById(R.id.timer_start); 340 mStart.setOnClickListener(new OnClickListener() { 341 @Override 342 public void onClick(View v) { 343 // New timer create if timer length is not zero 344 // Create a new timer object to track the timer and 345 // switch to the timers view. 346 int timerLength = mTimerSetup.getTime(); 347 if (timerLength == 0) { 348 return; 349 } 350 TimerObj t = new TimerObj(timerLength * 1000); 351 t.mState = TimerObj.STATE_RUNNING; 352 mAdapter.addTimer(t); 353 updateTimersState(t, Timers.START_TIMER); 354 gotoTimersView(); 355 } 356 357 }); 358 mAddTimer = (ImageButton)v.findViewById(R.id.timer_add_timer); 359 mAddTimer.setOnClickListener(new OnClickListener() { 360 @Override 361 public void onClick(View v) { 362 mTimerSetup.reset(); 363 gotoSetupView(); 364 } 365 366 }); 367 mPrefs = PreferenceManager.getDefaultSharedPreferences(getActivity()); 368 mNotificationManager = (NotificationManager) 369 getActivity().getSystemService(Context.NOTIFICATION_SERVICE); 370 371 return v; 372 } 373 374 @Override 375 public void onResume() { 376 super.onResume(); 377 mPrefs.registerOnSharedPreferenceChangeListener(this); 378 379 mAdapter = createAdapter(getActivity(), mPrefs); 380 mAdapter.onRestoreInstanceState(null); 381 382 if (mPrefs.getBoolean(Timers.FROM_NOTIFICATION, false)) { 383 // We need to know if this onresume is being called by the user clicking a 384 // buzzing timer notification. If so, we need to set that timer to have "stopped" 385 // at the moment the notification was hit. 386 long now = mPrefs.getLong(Timers.NOTIF_TIME, System.currentTimeMillis()); 387 int timerId = mPrefs.getInt(Timers.NOTIF_ID, -1); 388 if (timerId != -1) { 389 TimerObj t = Timers.findTimer(mAdapter.mTimers, timerId); 390 t.mTimeLeft = t.mOriginalLength - (now - t.mStartTime); 391 cancelTimerNotification(timerId); 392 } 393 SharedPreferences.Editor editor = mPrefs.edit(); 394 editor.putBoolean(Timers.FROM_NOTIFICATION, false); 395 editor.apply(); 396 } 397 398 mTimersList.setAdapter(mAdapter); 399 if (mAdapter.getCount() == 0) { 400 mCancel.setVisibility(View.INVISIBLE); 401 } 402 403 setPage(); 404 } 405 406 @Override 407 public void onPause() { 408 super.onPause(); 409 stopClockTicks(); 410 saveGlobalState(); 411 mPrefs.unregisterOnSharedPreferenceChangeListener(this); 412 } 413 414 @Override 415 public void onSaveInstanceState (Bundle outState) { 416 super.onSaveInstanceState(outState); 417 if (mAdapter != null) { 418 mAdapter.onSaveInstanceState (outState); 419 } 420 } 421 422 @Override 423 public void saveGlobalState () { 424 super.saveGlobalState(); 425 if (mAdapter != null) { 426 mAdapter.saveGlobalState (); 427 } 428 } 429 430 public void setPage() { 431 if (mAdapter.getCount() != 0) { 432 gotoTimersView(); 433 } else { 434 gotoSetupView(); 435 } 436 } 437 438 public void stopAllTimesUpTimers() { 439 while (0 < mAdapter.getCount()) { 440 TimerObj timerObj = (TimerObj) mAdapter.getItem(0); 441 if (timerObj.mState == TimerObj.STATE_TIMESUP) { 442 onStopButtonPressed(timerObj); 443 } 444 } 445 } 446 447 private void gotoSetupView() { 448 mNewTimerPage.setVisibility(View.VISIBLE); 449 mTimersListPage.setVisibility(View.GONE); 450 stopClockTicks(); 451 if (mAdapter.getCount() == 0) { 452 mCancel.setVisibility(View.INVISIBLE); 453 } else { 454 mCancel.setVisibility(View.VISIBLE); 455 } 456 } 457 private void gotoTimersView() { 458 mNewTimerPage.setVisibility(View.GONE); 459 mTimersListPage.setVisibility(View.VISIBLE); 460 startClockTicks(); 461 } 462 463 @Override 464 public void onClick(View v) { 465 ClickAction tag = (ClickAction) v.getTag(); 466 onClickHelper(tag); 467 } 468 469 private void onClickHelper(ClickAction clickAction) { 470 switch (clickAction.mAction) { 471 case ClickAction.ACTION_DELETE: 472 TimerObj t = clickAction.mTimer; 473 if (t.mState == TimerObj.STATE_TIMESUP) { 474 cancelTimerNotification(t.mTimerId); 475 } 476 mAdapter.deleteTimer(t.mTimerId); 477 if (mAdapter.getCount() == 0) { 478 if (mOnEmptyListListener == null) { 479 mTimerSetup.reset(); 480 gotoSetupView(); 481 } else { 482 mOnEmptyListListener.onEmptyList(); 483 } 484 } 485 // Tell receiver the timer was deleted. 486 // It will stop all activity related to the timer 487 updateTimersState(t, Timers.DELETE_TIMER); 488 break; 489 case ClickAction.ACTION_PLUS_ONE: 490 onPlusOneButtonPressed(clickAction.mTimer); 491 setTimerButtons(clickAction.mTimer); 492 break; 493 case ClickAction.ACTION_STOP: 494 onStopButtonPressed(clickAction.mTimer); 495 setTimerButtons(clickAction.mTimer); 496 break; 497 default: 498 break; 499 } 500 } 501 502 private void onPlusOneButtonPressed(TimerObj t) { 503 switch(t.mState) { 504 case TimerObj.STATE_RUNNING: 505 t.addTime(60000); //60 seconds in millis 506 long timeLeft = t.updateTimeLeft(false); 507 ((TimerListItem)(t.mView)).setTime(timeLeft, false); 508 ((TimerListItem)(t.mView)).setLength(timeLeft); 509 mAdapter.notifyDataSetChanged(); 510 updateTimersState(t, Timers.TIMER_UPDATE); 511 break; 512 case TimerObj.STATE_TIMESUP: 513 // +1 min when the time is up will restart the timer with 1 minute left. 514 t.mState = TimerObj.STATE_RUNNING; 515 t.mStartTime = System.currentTimeMillis(); 516 t.mTimeLeft = t. mOriginalLength = 60000; 517 ((TimerListItem)t.mView).setTime(t.mTimeLeft, false); 518 ((TimerListItem)t.mView).set(t.mOriginalLength, t.mTimeLeft, true); 519 ((TimerListItem) t.mView).start(); 520 updateTimersState(t, Timers.TIMER_RESET); 521 updateTimersState(t, Timers.START_TIMER); 522 updateTimesUpMode(t); 523 cancelTimerNotification(t.mTimerId); 524 break; 525 case TimerObj.STATE_STOPPED: 526 case TimerObj.STATE_DONE: 527 t.mState = TimerObj.STATE_RESTART; 528 t.mTimeLeft = t. mOriginalLength = t.mSetupLength; 529 ((TimerListItem)t.mView).stop(); 530 ((TimerListItem)t.mView).setTime(t.mTimeLeft, false); 531 ((TimerListItem)t.mView).set(t.mOriginalLength, t.mTimeLeft, false); 532 updateTimersState(t, Timers.TIMER_RESET); 533 break; 534 default: 535 break; 536 } 537 } 538 539 540 541 542 private void onStopButtonPressed(TimerObj t) { 543 switch(t.mState) { 544 case TimerObj.STATE_RUNNING: 545 // Stop timer and save the remaining time of the timer 546 t.mState = TimerObj.STATE_STOPPED; 547 ((TimerListItem) t.mView).pause(); 548 t.updateTimeLeft(true); 549 updateTimersState(t, Timers.TIMER_STOP); 550 break; 551 case TimerObj.STATE_STOPPED: 552 // Reset the remaining time and continue timer 553 t.mState = TimerObj.STATE_RUNNING; 554 t.mStartTime = System.currentTimeMillis() - (t.mOriginalLength - t.mTimeLeft); 555 ((TimerListItem) t.mView).start(); 556 updateTimersState(t, Timers.START_TIMER); 557 break; 558 case TimerObj.STATE_TIMESUP: 559 t.mState = TimerObj.STATE_DONE; 560 ((TimerListItem) t.mView).done(); 561 updateTimersState(t, Timers.TIMER_DONE); 562 cancelTimerNotification(t.mTimerId); 563 updateTimesUpMode(t); 564 break; 565 case TimerObj.STATE_DONE: 566 break; 567 case TimerObj.STATE_RESTART: 568 t.mState = TimerObj.STATE_RUNNING; 569 t.mStartTime = System.currentTimeMillis() - (t.mOriginalLength - t.mTimeLeft); 570 ((TimerListItem) t.mView).start(); 571 updateTimersState(t, Timers.START_TIMER); 572 break; 573 default: 574 break; 575 } 576 } 577 578 private void setTimerButtons(TimerObj t) { 579 Context a = getActivity(); 580 if (a == null || t == null || t.mView == null) { 581 return; 582 } 583 ImageButton plusOne = (ImageButton) t.mView.findViewById(R.id.timer_plus_one); 584 ImageButton stop = (ImageButton) t.mView.findViewById(R.id.timer_stop); 585 CountingTimerView countingTimerView = (CountingTimerView) 586 t.mView.findViewById(R.id.timer_time_text); 587 Resources r = a.getResources(); 588 switch (t.mState) { 589 case TimerObj.STATE_RUNNING: 590 plusOne.setVisibility(View.VISIBLE); 591 plusOne.setContentDescription(r.getString(R.string.timer_plus_one)); 592 plusOne.setImageResource(R.drawable.ic_plusone); 593 stop.setContentDescription(r.getString(R.string.timer_stop)); 594 stop.setImageResource(R.drawable.ic_stop_normal); 595 stop.setEnabled(true); 596 countingTimerView.setVirtualButtonEnabled(true); 597 break; 598 case TimerObj.STATE_STOPPED: 599 plusOne.setVisibility(View.VISIBLE); 600 plusOne.setContentDescription(r.getString(R.string.timer_reset)); 601 plusOne.setImageResource(R.drawable.ic_reset); 602 stop.setContentDescription(r.getString(R.string.timer_start)); 603 stop.setImageResource(R.drawable.ic_start_normal); 604 stop.setEnabled(true); 605 countingTimerView.setVirtualButtonEnabled(true); 606 break; 607 case TimerObj.STATE_TIMESUP: 608 plusOne.setVisibility(View.VISIBLE); 609 plusOne.setImageResource(R.drawable.ic_plusone); 610 stop.setContentDescription(r.getString(R.string.timer_stop)); 611 stop.setEnabled(true); 612 countingTimerView.setVirtualButtonEnabled(true); 613 break; 614 case TimerObj.STATE_DONE: 615 plusOne.setVisibility(View.VISIBLE); 616 plusOne.setContentDescription(r.getString(R.string.timer_reset)); 617 plusOne.setImageResource(R.drawable.ic_reset); 618 stop.setContentDescription(r.getString(R.string.timer_start)); 619 stop.setImageResource(R.drawable.ic_start_disabled); 620 stop.setEnabled(false); 621 countingTimerView.setVirtualButtonEnabled(false); 622 break; 623 case TimerObj.STATE_RESTART: 624 plusOne.setVisibility(View.INVISIBLE); 625 stop.setContentDescription(r.getString(R.string.timer_start)); 626 stop.setImageResource(R.drawable.ic_start_normal); 627 stop.setEnabled(true); 628 countingTimerView.setVirtualButtonEnabled(true); 629 break; 630 default: 631 break; 632 } 633 } 634 635 private void startClockTicks() { 636 mTimersList.postDelayed(mClockTick, 20); 637 mTicking = true; 638 } 639 private void stopClockTicks() { 640 if (mTicking) { 641 mTimersList.removeCallbacks(mClockTick); 642 mTicking = false; 643 } 644 } 645 646 private void updateTimersState(TimerObj t, String action) { 647 if (!Timers.DELETE_TIMER.equals(action)) { 648 t.writeToSharedPref(mPrefs); 649 } 650 Intent i = new Intent(); 651 i.setAction(action); 652 i.putExtra(Timers.TIMER_INTENT_EXTRA, t.mTimerId); 653 getActivity().sendBroadcast(i); 654 } 655 656 private void cancelTimerNotification(int timerId) { 657 mNotificationManager.cancel(timerId); 658 } 659 660 private void updateTimesUpMode(TimerObj timerObj) { 661 if (mOnEmptyListListener != null && timerObj.mState != TimerObj.STATE_TIMESUP) { 662 mAdapter.removeTimer(timerObj); 663 if (mAdapter.getCount() == 0) { 664 mOnEmptyListListener.onEmptyList(); 665 } 666 } 667 } 668 669 public void restartAdapter() { 670 mAdapter = createAdapter(getActivity(), mPrefs); 671 mAdapter.onRestoreInstanceState(null); 672 } 673 674 @Override 675 public void onSharedPreferenceChanged(SharedPreferences prefs, String key) { 676 if (prefs.equals(mPrefs)) { 677 if ( (key.equals(Timers.FROM_NOTIFICATION) || key.equals(Timers.NOTIF_ID) 678 || key.equals(Timers.NOTIF_TIME)) && 679 prefs.getBoolean(Timers.FROM_NOTIFICATION, false) ) { 680 // We need to know if the user has clicked the buzzing timer notification 681 // while the fragment is still open. If so, this listener will catch that event, 682 // and allow the timers to be re-instated based on the updated stop time. 683 // Because this method gets called with every change to the sharedprefs, we ensure 684 // that we only recalculate the timers if the change was specifically set by the 685 // user interacting with the notification. 686 long now = prefs.getLong(Timers.NOTIF_TIME, System.currentTimeMillis()); 687 int timerId = prefs.getInt(Timers.NOTIF_ID, -1); 688 mAdapter = createAdapter(getActivity(), mPrefs); 689 mAdapter.onRestoreInstanceState(null); 690 if (timerId != -1) { 691 TimerObj t = Timers.findTimer(mAdapter.mTimers, timerId); 692 t.mTimeLeft = t.mOriginalLength - (now - t.mStartTime); 693 cancelTimerNotification(timerId); 694 } 695 mTimersList.setAdapter(mAdapter); 696 SharedPreferences.Editor editor = prefs.edit(); 697 editor.putBoolean(Timers.FROM_NOTIFICATION, false); 698 editor.apply(); 699 } 700 } 701 } 702 703 704} 705