1 package com.android.deskclock.stopwatch; 2 3 import android.app.Activity; 4 import android.content.Context; 5 import android.content.Intent; 6 import android.content.SharedPreferences; 7 import android.content.SharedPreferences.OnSharedPreferenceChangeListener; 8 import android.content.pm.PackageManager; 9 import android.content.pm.ResolveInfo; 10 import android.graphics.drawable.Drawable; 11 import android.os.Bundle; 12 import android.os.PowerManager; 13 import android.os.PowerManager.WakeLock; 14 import android.preference.PreferenceManager; 15 import android.text.format.DateUtils; 16 import android.view.LayoutInflater; 17 import android.view.View; 18 import android.view.ViewGroup; 19 import android.widget.AdapterView; 20 import android.widget.AdapterView.OnItemClickListener; 21 import android.widget.ArrayAdapter; 22 import android.widget.BaseAdapter; 23 import android.widget.ImageButton; 24 import android.widget.ImageView; 25 import android.widget.ListPopupWindow; 26 import android.widget.ListView; 27 import android.widget.PopupWindow.OnDismissListener; 28 import android.widget.TextView; 29 30 import com.android.deskclock.CircleButtonsLinearLayout; 31 import com.android.deskclock.CircleTimerView; 32 import com.android.deskclock.DeskClock; 33 import com.android.deskclock.DeskClockFragment; 34 import com.android.deskclock.Log; 35 import com.android.deskclock.R; 36 import com.android.deskclock.Utils; 37 import com.android.deskclock.timer.CountingTimerView; 38 39 import java.util.ArrayList; 40 import java.util.List; 41 42 public class StopwatchFragment extends DeskClockFragment 43 implements OnSharedPreferenceChangeListener { 44 45 private static final String TAG = "StopwatchFragment"; 46 int mState = Stopwatches.STOPWATCH_RESET; 47 48 // Stopwatch views that are accessed by the activity 49 private ImageButton mLeftButton; 50 private TextView mCenterButton; 51 private CircleTimerView mTime; 52 private CountingTimerView mTimeText; 53 private ListView mLapsList; 54 private ImageButton mShareButton; 55 private ListPopupWindow mSharePopup; 56 private WakeLock mWakeLock; 57 58 // Used for calculating the time from the start taking into account the pause times 59 long mStartTime = 0; 60 long mAccumulatedTime = 0; 61 62 // Lap information 63 class Lap { 64 Lap () { 65 mLapTime = 0; 66 mTotalTime = 0; 67 } 68 69 Lap (long time, long total) { 70 mLapTime = time; 71 mTotalTime = total; 72 } 73 public long mLapTime; 74 public long mTotalTime; 75 } 76 77 // Adapter for the ListView that shows the lap times. 78 class LapsListAdapter extends BaseAdapter { 79 80 ArrayList<Lap> mLaps = new ArrayList<Lap>(); 81 private final LayoutInflater mInflater; 82 private final int mBackgroundColor; 83 private final String[] mFormats; 84 private final String[] mLapFormatSet; 85 // Size of this array must match the size of formats 86 private final long[] mThresholds = { 87 10 * DateUtils.MINUTE_IN_MILLIS, // < 10 minutes 88 DateUtils.HOUR_IN_MILLIS, // < 1 hour 89 10 * DateUtils.HOUR_IN_MILLIS, // < 10 hours 90 100 * DateUtils.HOUR_IN_MILLIS, // < 100 hours 91 1000 * DateUtils.HOUR_IN_MILLIS // < 1000 hours 92 }; 93 private int mLapIndex = 0; 94 private int mTotalIndex = 0; 95 private String mLapFormat; 96 97 public LapsListAdapter(Context context) { 98 mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 99 mBackgroundColor = getResources().getColor(R.color.blackish); 100 mFormats = context.getResources().getStringArray(R.array.stopwatch_format_set); 101 mLapFormatSet = context.getResources().getStringArray(R.array.sw_lap_number_set); 102 updateLapFormat(); 103 } 104 105 @Override 106 public long getItemId(int position) { 107 return position; 108 } 109 110 @Override 111 public View getView(int position, View convertView, ViewGroup parent) { 112 if (mLaps.size() == 0 || position >= mLaps.size()) { 113 return null; 114 } 115 View lapInfo; 116 if (convertView != null) { 117 lapInfo = convertView; 118 } else { 119 lapInfo = mInflater.inflate(R.layout.lap_view, parent, false); 120 } 121 TextView count = (TextView)lapInfo.findViewById(R.id.lap_number); 122 TextView lapTime = (TextView)lapInfo.findViewById(R.id.lap_time); 123 TextView toalTime = (TextView)lapInfo.findViewById(R.id.lap_total); 124 lapTime.setText(Stopwatches.formatTimeText(mLaps.get(position).mLapTime, 125 mFormats[mLapIndex])); 126 toalTime.setText(Stopwatches.formatTimeText(mLaps.get(position).mTotalTime, 127 mFormats[mTotalIndex])); 128 count.setText(String.format(mLapFormat, mLaps.size() - position).toUpperCase()); 129 130 lapInfo.setBackgroundColor(mBackgroundColor); 131 return lapInfo; 132 } 133 134 @Override 135 public int getCount() { 136 return mLaps.size(); 137 } 138 139 @Override 140 public Object getItem(int position) { 141 if (mLaps.size() == 0 || position >= mLaps.size()) { 142 return null; 143 } 144 return mLaps.get(position); 145 } 146 147 private void updateLapFormat() { 148 // Note Stopwatches.MAX_LAPS < 100 149 mLapFormat = mLapFormatSet[mLaps.size() < 10 ? 0 : 1]; 150 } 151 152 private void resetTimeFormats() { 153 mLapIndex = mTotalIndex = 0; 154 } 155 156 public boolean updateTimeFormats(Lap lap) { 157 boolean formatChanged = false; 158 while (mLapIndex + 1 < mThresholds.length && lap.mLapTime >= mThresholds[mLapIndex]) { 159 mLapIndex++; 160 formatChanged = true; 161 } 162 while (mTotalIndex + 1 < mThresholds.length && 163 lap.mTotalTime >= mThresholds[mTotalIndex]) { 164 mTotalIndex++; 165 formatChanged = true; 166 } 167 return formatChanged; 168 } 169 170 public void addLap(Lap l) { 171 mLaps.add(0, l); 172 // for efficiency caller also calls notifyDataSetChanged() 173 } 174 175 public void clearLaps() { 176 mLaps.clear(); 177 updateLapFormat(); 178 resetTimeFormats(); 179 notifyDataSetChanged(); 180 } 181 182 // Helper function used to get the lap data to be stored in the activitys's bundle 183 public long [] getLapTimes() { 184 int size = mLaps.size(); 185 if (size == 0) { 186 return null; 187 } 188 long [] laps = new long[size]; 189 for (int i = 0; i < size; i ++) { 190 laps[i] = mLaps.get(i).mTotalTime; 191 } 192 return laps; 193 } 194 195 // Helper function to restore adapter's data from the activity's bundle 196 public void setLapTimes(long [] laps) { 197 if (laps == null || laps.length == 0) { 198 return; 199 } 200 201 int size = laps.length; 202 mLaps.clear(); 203 for (int i = 0; i < size; i ++) { 204 mLaps.add(new Lap (laps[i], 0)); 205 } 206 long totalTime = 0; 207 for (int i = size -1; i >= 0; i --) { 208 totalTime += laps[i]; 209 mLaps.get(i).mTotalTime = totalTime; 210 updateTimeFormats(mLaps.get(i)); 211 } 212 updateLapFormat(); 213 notifyDataSetChanged(); 214 } 215 } 216 217 // Keys for data stored in the activity's bundle 218 private static final String START_TIME_KEY = "start_time"; 219 private static final String ACCUM_TIME_KEY = "accum_time"; 220 private static final String STATE_KEY = "state"; 221 private static final String LAPS_KEY = "laps"; 222 223 LapsListAdapter mLapsAdapter; 224 225 public StopwatchFragment() { 226 } 227 228 private void rightButtonAction() { 229 long time = Utils.getTimeNow(); 230 Context context = getActivity().getApplicationContext(); 231 Intent intent = new Intent(context, StopwatchService.class); 232 intent.putExtra(Stopwatches.MESSAGE_TIME, time); 233 intent.putExtra(Stopwatches.SHOW_NOTIF, false); 234 switch (mState) { 235 case Stopwatches.STOPWATCH_RUNNING: 236 // do stop 237 long curTime = Utils.getTimeNow(); 238 mAccumulatedTime += (curTime - mStartTime); 239 doStop(); 240 intent.setAction(Stopwatches.STOP_STOPWATCH); 241 context.startService(intent); 242 releaseWakeLock(); 243 break; 244 case Stopwatches.STOPWATCH_RESET: 245 case Stopwatches.STOPWATCH_STOPPED: 246 // do start 247 doStart(time); 248 intent.setAction(Stopwatches.START_STOPWATCH); 249 context.startService(intent); 250 acquireWakeLock(); 251 break; 252 default: 253 Log.wtf("Illegal state " + mState 254 + " while pressing the right stopwatch button"); 255 break; 256 } 257 } 258 259 @Override 260 public View onCreateView(LayoutInflater inflater, ViewGroup container, 261 Bundle savedInstanceState) { 262 // Inflate the layout for this fragment 263 View v = inflater.inflate(R.layout.stopwatch_fragment, container, false); 264 265 mLeftButton = (ImageButton)v.findViewById(R.id.stopwatch_left_button); 266 mLeftButton.setOnClickListener(new View.OnClickListener() { 267 @Override 268 public void onClick(View v) { 269 long time = Utils.getTimeNow(); 270 Context context = getActivity().getApplicationContext(); 271 Intent intent = new Intent(context, StopwatchService.class); 272 intent.putExtra(Stopwatches.MESSAGE_TIME, time); 273 intent.putExtra(Stopwatches.SHOW_NOTIF, false); 274 switch (mState) { 275 case Stopwatches.STOPWATCH_RUNNING: 276 // Save lap time 277 addLapTime(time); 278 doLap(); 279 intent.setAction(Stopwatches.LAP_STOPWATCH); 280 context.startService(intent); 281 break; 282 case Stopwatches.STOPWATCH_STOPPED: 283 // do reset 284 doReset(); 285 intent.setAction(Stopwatches.RESET_STOPWATCH); 286 context.startService(intent); 287 releaseWakeLock(); 288 break; 289 default: 290 Log.wtf("Illegal state " + mState 291 + " while pressing the left stopwatch button"); 292 break; 293 } 294 } 295 }); 296 297 298 mCenterButton = (TextView)v.findViewById(R.id.stopwatch_stop); 299 mShareButton = (ImageButton)v.findViewById(R.id.stopwatch_share_button); 300 301 mShareButton.setOnClickListener(new View.OnClickListener() { 302 @Override 303 public void onClick(View v) { 304 showSharePopup(); 305 } 306 }); 307 308 // Timer text serves as a virtual start/stop button. 309 final CountingTimerView countingTimerView = (CountingTimerView) 310 v.findViewById(R.id.stopwatch_time_text); 311 countingTimerView.registerVirtualButtonAction(new Runnable() { 312 @Override 313 public void run() { 314 rightButtonAction(); 315 } 316 }); 317 countingTimerView.registerStopTextView(mCenterButton); 318 countingTimerView.setVirtualButtonEnabled(true); 319 320 mTime = (CircleTimerView)v.findViewById(R.id.stopwatch_time); 321 mTimeText = (CountingTimerView)v.findViewById(R.id.stopwatch_time_text); 322 mLapsList = (ListView)v.findViewById(R.id.laps_list); 323 mLapsList.setDividerHeight(0); 324 mLapsAdapter = new LapsListAdapter(getActivity()); 325 if (mLapsList != null) { 326 mLapsList.setAdapter(mLapsAdapter); 327 } 328 329 CircleButtonsLinearLayout circleLayout = 330 (CircleButtonsLinearLayout)v.findViewById(R.id.stopwatch_circle); 331 circleLayout.setCircleTimerViewIds(R.id.stopwatch_time, R.id.stopwatch_left_button, 332 R.id.stopwatch_share_button, R.id.stopwatch_stop, 333 R.dimen.plusone_reset_button_padding, R.dimen.share_button_padding, 334 0, 0); /** No label for a stopwatch**/ 335 336 return v; 337 } 338 339 @Override 340 public void onResume() { 341 SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getActivity()); 342 prefs.registerOnSharedPreferenceChangeListener(this); 343 readFromSharedPref(prefs); 344 mTime.readFromSharedPref(prefs, "sw"); 345 mTime.postInvalidate(); 346 347 setButtons(mState); 348 mTimeText.setTime(mAccumulatedTime, true, true); 349 if (mState == Stopwatches.STOPWATCH_RUNNING) { 350 acquireWakeLock(); 351 startUpdateThread(); 352 } else if (mState == Stopwatches.STOPWATCH_STOPPED && mAccumulatedTime != 0) { 353 mTimeText.blinkTimeStr(true); 354 } 355 showLaps(); 356 ((DeskClock)getActivity()).registerPageChangedListener(this); 357 // View was hidden in onPause, make sure it is visible now. 358 View v = getView(); 359 if (v != null) { 360 getView().setVisibility(View.VISIBLE); 361 } 362 super.onResume(); 363 } 364 365 @Override 366 public void onPause() { 367 // This is called because the lock screen was activated, the window stay 368 // active under it and when we unlock the screen, we see the old time for 369 // a fraction of a second. 370 View v = getView(); 371 if (v != null) { 372 getView().setVisibility(View.INVISIBLE); 373 } 374 375 if (mState == Stopwatches.STOPWATCH_RUNNING) { 376 stopUpdateThread(); 377 } 378 // The stopwatch must keep running even if the user closes the app so save stopwatch state 379 // in shared prefs 380 SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getActivity()); 381 prefs.unregisterOnSharedPreferenceChangeListener(this); 382 writeToSharedPref(prefs); 383 mTime.writeToSharedPref(prefs, "sw"); 384 mTimeText.blinkTimeStr(false); 385 if (mSharePopup != null) { 386 mSharePopup.dismiss(); 387 mSharePopup = null; 388 } 389 ((DeskClock)getActivity()).unregisterPageChangedListener(this); 390 releaseWakeLock(); 391 super.onPause(); 392 } 393 394 @Override 395 public void onPageChanged(int page) { 396 if (page == DeskClock.STOPWATCH_TAB_INDEX && mState == Stopwatches.STOPWATCH_RUNNING) { 397 acquireWakeLock(); 398 } else { 399 releaseWakeLock(); 400 } 401 } 402 403 private void doStop() { 404 stopUpdateThread(); 405 mTime.pauseIntervalAnimation(); 406 mTimeText.setTime(mAccumulatedTime, true, true); 407 mTimeText.blinkTimeStr(true); 408 updateCurrentLap(mAccumulatedTime); 409 setButtons(Stopwatches.STOPWATCH_STOPPED); 410 mState = Stopwatches.STOPWATCH_STOPPED; 411 } 412 413 private void doStart(long time) { 414 mStartTime = time; 415 startUpdateThread(); 416 mTimeText.blinkTimeStr(false); 417 if (mTime.isAnimating()) { 418 mTime.startIntervalAnimation(); 419 } 420 setButtons(Stopwatches.STOPWATCH_RUNNING); 421 mState = Stopwatches.STOPWATCH_RUNNING; 422 } 423 424 private void doLap() { 425 showLaps(); 426 setButtons(Stopwatches.STOPWATCH_RUNNING); 427 } 428 429 private void doReset() { 430 SharedPreferences prefs = 431 PreferenceManager.getDefaultSharedPreferences(getActivity()); 432 Utils.clearSwSharedPref(prefs); 433 mTime.clearSharedPref(prefs, "sw"); 434 mAccumulatedTime = 0; 435 mLapsAdapter.clearLaps(); 436 showLaps(); 437 mTime.stopIntervalAnimation(); 438 mTime.reset(); 439 mTimeText.setTime(mAccumulatedTime, true, true); 440 mTimeText.blinkTimeStr(false); 441 setButtons(Stopwatches.STOPWATCH_RESET); 442 mState = Stopwatches.STOPWATCH_RESET; 443 } 444 445 private void showShareButton(boolean show) { 446 if (mShareButton != null) { 447 mShareButton.setVisibility(show ? View.VISIBLE : View.INVISIBLE); 448 mShareButton.setEnabled(show); 449 } 450 } 451 452 private void showSharePopup() { 453 Intent intent = getShareIntent(); 454 455 Activity parent = getActivity(); 456 PackageManager packageManager = parent.getPackageManager(); 457 458 // Get a list of sharable options. 459 List<ResolveInfo> shareOptions = packageManager 460 .queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY); 461 462 if (shareOptions.size() == 0) { 463 return; 464 } 465 ArrayList<CharSequence> shareOptionTitles = new ArrayList<CharSequence>(); 466 ArrayList<Drawable> shareOptionIcons = new ArrayList<Drawable>(); 467 ArrayList<CharSequence> shareOptionThreeTitles = new ArrayList<CharSequence>(); 468 ArrayList<Drawable> shareOptionThreeIcons = new ArrayList<Drawable>(); 469 ArrayList<String> shareOptionPackageNames = new ArrayList<String>(); 470 ArrayList<String> shareOptionClassNames = new ArrayList<String>(); 471 472 for (int option_i = 0; option_i < shareOptions.size(); option_i++) { 473 ResolveInfo option = shareOptions.get(option_i); 474 CharSequence label = option.loadLabel(packageManager); 475 Drawable icon = option.loadIcon(packageManager); 476 shareOptionTitles.add(label); 477 shareOptionIcons.add(icon); 478 if (shareOptions.size() > 4 && option_i < 3) { 479 shareOptionThreeTitles.add(label); 480 shareOptionThreeIcons.add(icon); 481 } 482 shareOptionPackageNames.add(option.activityInfo.packageName); 483 shareOptionClassNames.add(option.activityInfo.name); 484 } 485 if (shareOptionTitles.size() > 4) { 486 shareOptionThreeTitles.add(getResources().getString(R.string.see_all)); 487 shareOptionThreeIcons.add(getResources().getDrawable(android.R.color.transparent)); 488 } 489 490 if (mSharePopup != null) { 491 mSharePopup.dismiss(); 492 mSharePopup = null; 493 } 494 mSharePopup = new ListPopupWindow(parent); 495 mSharePopup.setAnchorView(mShareButton); 496 mSharePopup.setModal(true); 497 // This adapter to show the rest will be used to quickly repopulate if "See all..." is hit. 498 ImageLabelAdapter showAllAdapter = new ImageLabelAdapter(parent, 499 R.layout.popup_window_item, shareOptionTitles, shareOptionIcons, 500 shareOptionPackageNames, shareOptionClassNames); 501 if (shareOptionTitles.size() > 4) { 502 mSharePopup.setAdapter(new ImageLabelAdapter(parent, R.layout.popup_window_item, 503 shareOptionThreeTitles, shareOptionThreeIcons, shareOptionPackageNames, 504 shareOptionClassNames, showAllAdapter)); 505 } else { 506 mSharePopup.setAdapter(showAllAdapter); 507 } 508 509 mSharePopup.setOnItemClickListener(new OnItemClickListener() { 510 @Override 511 public void onItemClick(AdapterView<?> parent, View view, int position, long id) { 512 CharSequence label = ((TextView) view.findViewById(R.id.title)).getText(); 513 if (label.equals(getResources().getString(R.string.see_all))) { 514 mSharePopup.setAdapter( 515 ((ImageLabelAdapter) parent.getAdapter()).getShowAllAdapter()); 516 mSharePopup.show(); 517 return; 518 } 519 520 Intent intent = getShareIntent(); 521 ImageLabelAdapter adapter = (ImageLabelAdapter) parent.getAdapter(); 522 String packageName = adapter.getPackageName(position); 523 String className = adapter.getClassName(position); 524 intent.setClassName(packageName, className); 525 startActivity(intent); 526 } 527 }); 528 mSharePopup.setOnDismissListener(new OnDismissListener() { 529 @Override 530 public void onDismiss() { 531 mSharePopup = null; 532 } 533 }); 534 mSharePopup.setWidth((int) getResources().getDimension(R.dimen.popup_window_width)); 535 mSharePopup.show(); 536 } 537 538 private Intent getShareIntent() { 539 Intent intent = new Intent(android.content.Intent.ACTION_SEND); 540 intent.setType("text/plain"); 541 intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET); 542 intent.putExtra(Intent.EXTRA_SUBJECT, 543 Stopwatches.getShareTitle(getActivity().getApplicationContext())); 544 intent.putExtra(Intent.EXTRA_TEXT, Stopwatches.buildShareResults( 545 getActivity().getApplicationContext(), mTimeText.getTimeString(), 546 getLapShareTimes(mLapsAdapter.getLapTimes()))); 547 return intent; 548 } 549 550 /** Turn laps as they would be saved in prefs into format for sharing. **/ 551 private long[] getLapShareTimes(long[] input) { 552 if (input == null) { 553 return null; 554 } 555 556 int numLaps = input.length; 557 long[] output = new long[numLaps]; 558 long prevLapElapsedTime = 0; 559 for (int lap_i = numLaps - 1; lap_i >= 0; lap_i--) { 560 long lap = input[lap_i]; 561 Log.v("lap "+lap_i+": "+lap); 562 output[lap_i] = lap - prevLapElapsedTime; 563 prevLapElapsedTime = lap; 564 } 565 return output; 566 } 567 568 /*** 569 * Update the buttons on the stopwatch according to the watch's state 570 */ 571 private void setButtons(int state) { 572 switch (state) { 573 case Stopwatches.STOPWATCH_RESET: 574 setButton(mLeftButton, R.string.sw_lap_button, R.drawable.ic_lap, false, 575 View.INVISIBLE); 576 setStartStopText(mCenterButton, R.string.sw_start_button); 577 showShareButton(false); 578 break; 579 case Stopwatches.STOPWATCH_RUNNING: 580 setButton(mLeftButton, R.string.sw_lap_button, R.drawable.ic_lap, 581 !reachedMaxLaps(), View.VISIBLE); 582 setStartStopText(mCenterButton, R.string.sw_stop_button); 583 showShareButton(false); 584 break; 585 case Stopwatches.STOPWATCH_STOPPED: 586 setButton(mLeftButton, R.string.sw_reset_button, R.drawable.ic_reset, true, 587 View.VISIBLE); 588 setStartStopText(mCenterButton, R.string.sw_start_button); 589 showShareButton(true); 590 break; 591 default: 592 break; 593 } 594 } 595 private boolean reachedMaxLaps() { 596 return mLapsAdapter.getCount() >= Stopwatches.MAX_LAPS; 597 } 598 599 /*** 600 * Set a single button with the string and states provided. 601 * @param b - Button view to update 602 * @param text - Text in button 603 * @param enabled - enable/disables the button 604 * @param visibility - Show/hide the button 605 */ 606 private void setButton( 607 ImageButton b, int text, int drawableId, boolean enabled, int visibility) { 608 b.setContentDescription(getActivity().getResources().getString(text)); 609 b.setImageResource(drawableId); 610 b.setVisibility(visibility); 611 b.setEnabled(enabled); 612 } 613 614 private void setStartStopText(TextView v, int text) { 615 String textStr = getActivity().getResources().getString(text); 616 v.setText(textStr); 617 v.setContentDescription(textStr); 618 } 619 620 /*** 621 * 622 * @param time - in hundredths of a second 623 */ 624 private void addLapTime(long time) { 625 int size = mLapsAdapter.getCount(); 626 long curTime = time - mStartTime + mAccumulatedTime; 627 if (size == 0) { 628 // Always show the ending lap and a new one 629 Lap firstLap = new Lap(curTime, curTime); 630 mLapsAdapter.addLap(firstLap); 631 mLapsAdapter.addLap(new Lap(0, curTime)); 632 mTime.setIntervalTime(curTime); 633 mLapsAdapter.updateTimeFormats(firstLap); 634 } else { 635 long lapTime = curTime - ((Lap) mLapsAdapter.getItem(1)).mTotalTime; 636 ((Lap)mLapsAdapter.getItem(0)).mLapTime = lapTime; 637 ((Lap)mLapsAdapter.getItem(0)).mTotalTime = curTime; 638 mLapsAdapter.addLap(new Lap(0, 0)); 639 mTime.setMarkerTime(lapTime); 640 mLapsAdapter.updateLapFormat(); 641 // mTime.setIntervalTime(lapTime * 10); 642 } 643 mLapsAdapter.notifyDataSetChanged(); 644 // Start lap animation starting from the second lap 645 mTime.stopIntervalAnimation(); 646 if (!reachedMaxLaps()) { 647 mTime.startIntervalAnimation(); 648 } 649 } 650 651 private void updateCurrentLap(long totalTime) { 652 if (mLapsAdapter.getCount() > 0) { 653 Lap curLap = (Lap)mLapsAdapter.getItem(0); 654 curLap.mLapTime = totalTime - ((Lap)mLapsAdapter.getItem(1)).mTotalTime; 655 curLap.mTotalTime = totalTime; 656 mLapsAdapter.notifyDataSetChanged(); 657 } 658 } 659 660 private void showLaps() { 661 if (mLapsAdapter.getCount() > 0) { 662 mLapsList.setVisibility(View.VISIBLE); 663 } else { 664 mLapsList.setVisibility(View.INVISIBLE); 665 } 666 } 667 668 private void startUpdateThread() { 669 mTime.post(mTimeUpdateThread); 670 } 671 672 private void stopUpdateThread() { 673 mTime.removeCallbacks(mTimeUpdateThread); 674 } 675 676 Runnable mTimeUpdateThread = new Runnable() { 677 @Override 678 public void run() { 679 long curTime = Utils.getTimeNow(); 680 long totalTime = mAccumulatedTime + (curTime - mStartTime); 681 if (mTime != null) { 682 mTimeText.setTime(totalTime, true, true); 683 } 684 if (mLapsAdapter.getCount() > 0) { 685 updateCurrentLap(totalTime); 686 } 687 mTime.postDelayed(mTimeUpdateThread, 10); 688 } 689 }; 690 691 private void writeToSharedPref(SharedPreferences prefs) { 692 SharedPreferences.Editor editor = prefs.edit(); 693 editor.putLong (Stopwatches.PREF_START_TIME, mStartTime); 694 editor.putLong (Stopwatches.PREF_ACCUM_TIME, mAccumulatedTime); 695 editor.putInt (Stopwatches.PREF_STATE, mState); 696 if (mLapsAdapter != null) { 697 long [] laps = mLapsAdapter.getLapTimes(); 698 if (laps != null) { 699 editor.putInt (Stopwatches.PREF_LAP_NUM, laps.length); 700 for (int i = 0; i < laps.length; i++) { 701 String key = Stopwatches.PREF_LAP_TIME + Integer.toString(laps.length - i); 702 editor.putLong (key, laps[i]); 703 } 704 } 705 } 706 if (mState == Stopwatches.STOPWATCH_RUNNING) { 707 editor.putLong(Stopwatches.NOTIF_CLOCK_BASE, mStartTime-mAccumulatedTime); 708 editor.putLong(Stopwatches.NOTIF_CLOCK_ELAPSED, -1); 709 editor.putBoolean(Stopwatches.NOTIF_CLOCK_RUNNING, true); 710 } else if (mState == Stopwatches.STOPWATCH_STOPPED) { 711 editor.putLong(Stopwatches.NOTIF_CLOCK_ELAPSED, mAccumulatedTime); 712 editor.putLong(Stopwatches.NOTIF_CLOCK_BASE, -1); 713 editor.putBoolean(Stopwatches.NOTIF_CLOCK_RUNNING, false); 714 } else if (mState == Stopwatches.STOPWATCH_RESET) { 715 editor.remove(Stopwatches.NOTIF_CLOCK_BASE); 716 editor.remove(Stopwatches.NOTIF_CLOCK_RUNNING); 717 editor.remove(Stopwatches.NOTIF_CLOCK_ELAPSED); 718 } 719 editor.putBoolean(Stopwatches.PREF_UPDATE_CIRCLE, false); 720 editor.apply(); 721 } 722 723 private void readFromSharedPref(SharedPreferences prefs) { 724 mStartTime = prefs.getLong(Stopwatches.PREF_START_TIME, 0); 725 mAccumulatedTime = prefs.getLong(Stopwatches.PREF_ACCUM_TIME, 0); 726 mState = prefs.getInt(Stopwatches.PREF_STATE, Stopwatches.STOPWATCH_RESET); 727 int numLaps = prefs.getInt(Stopwatches.PREF_LAP_NUM, Stopwatches.STOPWATCH_RESET); 728 if (mLapsAdapter != null) { 729 long[] oldLaps = mLapsAdapter.getLapTimes(); 730 if (oldLaps == null || oldLaps.length < numLaps) { 731 long[] laps = new long[numLaps]; 732 long prevLapElapsedTime = 0; 733 for (int lap_i = 0; lap_i < numLaps; lap_i++) { 734 String key = Stopwatches.PREF_LAP_TIME + Integer.toString(lap_i + 1); 735 long lap = prefs.getLong(key, 0); 736 laps[numLaps - lap_i - 1] = lap - prevLapElapsedTime; 737 prevLapElapsedTime = lap; 738 } 739 mLapsAdapter.setLapTimes(laps); 740 } 741 } 742 if (prefs.getBoolean(Stopwatches.PREF_UPDATE_CIRCLE, true)) { 743 if (mState == Stopwatches.STOPWATCH_STOPPED) { 744 doStop(); 745 } else if (mState == Stopwatches.STOPWATCH_RUNNING) { 746 doStart(mStartTime); 747 } else if (mState == Stopwatches.STOPWATCH_RESET) { 748 doReset(); 749 } 750 } 751 } 752 753 public class ImageLabelAdapter extends ArrayAdapter<CharSequence> { 754 private final ArrayList<CharSequence> mStrings; 755 private final ArrayList<Drawable> mDrawables; 756 private final ArrayList<String> mPackageNames; 757 private final ArrayList<String> mClassNames; 758 private ImageLabelAdapter mShowAllAdapter; 759 760 public ImageLabelAdapter(Context context, int textViewResourceId, 761 ArrayList<CharSequence> strings, ArrayList<Drawable> drawables, 762 ArrayList<String> packageNames, ArrayList<String> classNames) { 763 super(context, textViewResourceId, strings); 764 mStrings = strings; 765 mDrawables = drawables; 766 mPackageNames = packageNames; 767 mClassNames = classNames; 768 } 769 770 // Use this constructor if showing a "see all" option, to pass in the adapter 771 // that will be needed to quickly show all the remaining options. 772 public ImageLabelAdapter(Context context, int textViewResourceId, 773 ArrayList<CharSequence> strings, ArrayList<Drawable> drawables, 774 ArrayList<String> packageNames, ArrayList<String> classNames, 775 ImageLabelAdapter showAllAdapter) { 776 super(context, textViewResourceId, strings); 777 mStrings = strings; 778 mDrawables = drawables; 779 mPackageNames = packageNames; 780 mClassNames = classNames; 781 mShowAllAdapter = showAllAdapter; 782 } 783 784 @Override 785 public View getView(int position, View convertView, ViewGroup parent) { 786 LayoutInflater li = getActivity().getLayoutInflater(); 787 View row = li.inflate(R.layout.popup_window_item, parent, false); 788 ((TextView) row.findViewById(R.id.title)).setText( 789 mStrings.get(position)); 790 ((ImageView) row.findViewById(R.id.icon)).setBackground( 791 mDrawables.get(position)); 792 return row; 793 } 794 795 public String getPackageName(int position) { 796 return mPackageNames.get(position); 797 } 798 799 public String getClassName(int position) { 800 return mClassNames.get(position); 801 } 802 803 public ImageLabelAdapter getShowAllAdapter() { 804 return mShowAllAdapter; 805 } 806 } 807 808 @Override 809 public void onSharedPreferenceChanged(SharedPreferences prefs, String key) { 810 if (prefs.equals(PreferenceManager.getDefaultSharedPreferences(getActivity()))) { 811 if (! (key.equals(Stopwatches.PREF_LAP_NUM) || 812 key.startsWith(Stopwatches.PREF_LAP_TIME))) { 813 readFromSharedPref(prefs); 814 if (prefs.getBoolean(Stopwatches.PREF_UPDATE_CIRCLE, true)) { 815 mTime.readFromSharedPref(prefs, "sw"); 816 } 817 } 818 } 819 } 820 821 // Used to keeps screen on when stopwatch is running. 822 823 private void acquireWakeLock() { 824 if (mWakeLock == null) { 825 final PowerManager pm = 826 (PowerManager) getActivity().getSystemService(Context.POWER_SERVICE); 827 mWakeLock = pm.newWakeLock( 828 PowerManager.SCREEN_BRIGHT_WAKE_LOCK | PowerManager.ON_AFTER_RELEASE, TAG); 829 mWakeLock.setReferenceCounted(false); 830 } 831 mWakeLock.acquire(); 832 } 833 834 private void releaseWakeLock() { 835 if (mWakeLock != null && mWakeLock.isHeld()) { 836 mWakeLock.release(); 837 } 838 } 839 840 } 841