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 { Lap()64 Lap () { 65 mLapTime = 0; 66 mTotalTime = 0; 67 } 68 Lap(long time, long total)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 LapsListAdapter(Context context)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 getItemId(int position)106 public long getItemId(int position) { 107 return position; 108 } 109 110 @Override getView(int position, View convertView, ViewGroup parent)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 getCount()135 public int getCount() { 136 return mLaps.size(); 137 } 138 139 @Override getItem(int position)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 updateLapFormat()147 private void updateLapFormat() { 148 // Note Stopwatches.MAX_LAPS < 100 149 mLapFormat = mLapFormatSet[mLaps.size() < 10 ? 0 : 1]; 150 } 151 resetTimeFormats()152 private void resetTimeFormats() { 153 mLapIndex = mTotalIndex = 0; 154 } 155 updateTimeFormats(Lap lap)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 addLap(Lap l)170 public void addLap(Lap l) { 171 mLaps.add(0, l); 172 // for efficiency caller also calls notifyDataSetChanged() 173 } 174 clearLaps()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 getLapTimes()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 setLapTimes(long [] laps)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 StopwatchFragment()225 public StopwatchFragment() { 226 } 227 rightButtonAction()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 onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)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 onResume()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 super.onResume(); 358 } 359 360 @Override onPause()361 public void onPause() { 362 if (mState == Stopwatches.STOPWATCH_RUNNING) { 363 stopUpdateThread(); 364 } 365 // The stopwatch must keep running even if the user closes the app so save stopwatch state 366 // in shared prefs 367 SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getActivity()); 368 prefs.unregisterOnSharedPreferenceChangeListener(this); 369 writeToSharedPref(prefs); 370 mTime.writeToSharedPref(prefs, "sw"); 371 mTimeText.blinkTimeStr(false); 372 if (mSharePopup != null) { 373 mSharePopup.dismiss(); 374 mSharePopup = null; 375 } 376 ((DeskClock)getActivity()).unregisterPageChangedListener(this); 377 releaseWakeLock(); 378 super.onPause(); 379 } 380 381 @Override onPageChanged(int page)382 public void onPageChanged(int page) { 383 if (page == DeskClock.STOPWATCH_TAB_INDEX && mState == Stopwatches.STOPWATCH_RUNNING) { 384 acquireWakeLock(); 385 } else { 386 releaseWakeLock(); 387 } 388 } 389 doStop()390 private void doStop() { 391 stopUpdateThread(); 392 mTime.pauseIntervalAnimation(); 393 mTimeText.setTime(mAccumulatedTime, true, true); 394 mTimeText.blinkTimeStr(true); 395 updateCurrentLap(mAccumulatedTime); 396 setButtons(Stopwatches.STOPWATCH_STOPPED); 397 mState = Stopwatches.STOPWATCH_STOPPED; 398 } 399 doStart(long time)400 private void doStart(long time) { 401 mStartTime = time; 402 startUpdateThread(); 403 mTimeText.blinkTimeStr(false); 404 if (mTime.isAnimating()) { 405 mTime.startIntervalAnimation(); 406 } 407 setButtons(Stopwatches.STOPWATCH_RUNNING); 408 mState = Stopwatches.STOPWATCH_RUNNING; 409 } 410 doLap()411 private void doLap() { 412 showLaps(); 413 setButtons(Stopwatches.STOPWATCH_RUNNING); 414 } 415 doReset()416 private void doReset() { 417 SharedPreferences prefs = 418 PreferenceManager.getDefaultSharedPreferences(getActivity()); 419 Utils.clearSwSharedPref(prefs); 420 mTime.clearSharedPref(prefs, "sw"); 421 mAccumulatedTime = 0; 422 mLapsAdapter.clearLaps(); 423 showLaps(); 424 mTime.stopIntervalAnimation(); 425 mTime.reset(); 426 mTimeText.setTime(mAccumulatedTime, true, true); 427 mTimeText.blinkTimeStr(false); 428 setButtons(Stopwatches.STOPWATCH_RESET); 429 mState = Stopwatches.STOPWATCH_RESET; 430 } 431 showShareButton(boolean show)432 private void showShareButton(boolean show) { 433 if (mShareButton != null) { 434 mShareButton.setVisibility(show ? View.VISIBLE : View.INVISIBLE); 435 mShareButton.setEnabled(show); 436 } 437 } 438 showSharePopup()439 private void showSharePopup() { 440 Intent intent = getShareIntent(); 441 442 Activity parent = getActivity(); 443 PackageManager packageManager = parent.getPackageManager(); 444 445 // Get a list of sharable options. 446 List<ResolveInfo> shareOptions = packageManager 447 .queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY); 448 449 if (shareOptions.size() == 0) { 450 return; 451 } 452 ArrayList<CharSequence> shareOptionTitles = new ArrayList<CharSequence>(); 453 ArrayList<Drawable> shareOptionIcons = new ArrayList<Drawable>(); 454 ArrayList<CharSequence> shareOptionThreeTitles = new ArrayList<CharSequence>(); 455 ArrayList<Drawable> shareOptionThreeIcons = new ArrayList<Drawable>(); 456 ArrayList<String> shareOptionPackageNames = new ArrayList<String>(); 457 ArrayList<String> shareOptionClassNames = new ArrayList<String>(); 458 459 for (int option_i = 0; option_i < shareOptions.size(); option_i++) { 460 ResolveInfo option = shareOptions.get(option_i); 461 CharSequence label = option.loadLabel(packageManager); 462 Drawable icon = option.loadIcon(packageManager); 463 shareOptionTitles.add(label); 464 shareOptionIcons.add(icon); 465 if (shareOptions.size() > 4 && option_i < 3) { 466 shareOptionThreeTitles.add(label); 467 shareOptionThreeIcons.add(icon); 468 } 469 shareOptionPackageNames.add(option.activityInfo.packageName); 470 shareOptionClassNames.add(option.activityInfo.name); 471 } 472 if (shareOptionTitles.size() > 4) { 473 shareOptionThreeTitles.add(getResources().getString(R.string.see_all)); 474 shareOptionThreeIcons.add(getResources().getDrawable(android.R.color.transparent)); 475 } 476 477 if (mSharePopup != null) { 478 mSharePopup.dismiss(); 479 mSharePopup = null; 480 } 481 mSharePopup = new ListPopupWindow(parent); 482 mSharePopup.setAnchorView(mShareButton); 483 mSharePopup.setModal(true); 484 // This adapter to show the rest will be used to quickly repopulate if "See all..." is hit. 485 ImageLabelAdapter showAllAdapter = new ImageLabelAdapter(parent, 486 R.layout.popup_window_item, shareOptionTitles, shareOptionIcons, 487 shareOptionPackageNames, shareOptionClassNames); 488 if (shareOptionTitles.size() > 4) { 489 mSharePopup.setAdapter(new ImageLabelAdapter(parent, R.layout.popup_window_item, 490 shareOptionThreeTitles, shareOptionThreeIcons, shareOptionPackageNames, 491 shareOptionClassNames, showAllAdapter)); 492 } else { 493 mSharePopup.setAdapter(showAllAdapter); 494 } 495 496 mSharePopup.setOnItemClickListener(new OnItemClickListener() { 497 @Override 498 public void onItemClick(AdapterView<?> parent, View view, int position, long id) { 499 CharSequence label = ((TextView) view.findViewById(R.id.title)).getText(); 500 if (label.equals(getResources().getString(R.string.see_all))) { 501 mSharePopup.setAdapter( 502 ((ImageLabelAdapter) parent.getAdapter()).getShowAllAdapter()); 503 mSharePopup.show(); 504 return; 505 } 506 507 Intent intent = getShareIntent(); 508 ImageLabelAdapter adapter = (ImageLabelAdapter) parent.getAdapter(); 509 String packageName = adapter.getPackageName(position); 510 String className = adapter.getClassName(position); 511 intent.setClassName(packageName, className); 512 startActivity(intent); 513 } 514 }); 515 mSharePopup.setOnDismissListener(new OnDismissListener() { 516 @Override 517 public void onDismiss() { 518 mSharePopup = null; 519 } 520 }); 521 mSharePopup.setWidth((int) getResources().getDimension(R.dimen.popup_window_width)); 522 mSharePopup.show(); 523 } 524 getShareIntent()525 private Intent getShareIntent() { 526 Intent intent = new Intent(android.content.Intent.ACTION_SEND); 527 intent.setType("text/plain"); 528 intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET); 529 intent.putExtra(Intent.EXTRA_SUBJECT, 530 Stopwatches.getShareTitle(getActivity().getApplicationContext())); 531 intent.putExtra(Intent.EXTRA_TEXT, Stopwatches.buildShareResults( 532 getActivity().getApplicationContext(), mTimeText.getTimeString(), 533 getLapShareTimes(mLapsAdapter.getLapTimes()))); 534 return intent; 535 } 536 537 /** Turn laps as they would be saved in prefs into format for sharing. **/ getLapShareTimes(long[] input)538 private long[] getLapShareTimes(long[] input) { 539 if (input == null) { 540 return null; 541 } 542 543 int numLaps = input.length; 544 long[] output = new long[numLaps]; 545 long prevLapElapsedTime = 0; 546 for (int lap_i = numLaps - 1; lap_i >= 0; lap_i--) { 547 long lap = input[lap_i]; 548 Log.v("lap "+lap_i+": "+lap); 549 output[lap_i] = lap - prevLapElapsedTime; 550 prevLapElapsedTime = lap; 551 } 552 return output; 553 } 554 555 /*** 556 * Update the buttons on the stopwatch according to the watch's state 557 */ setButtons(int state)558 private void setButtons(int state) { 559 switch (state) { 560 case Stopwatches.STOPWATCH_RESET: 561 setButton(mLeftButton, R.string.sw_lap_button, R.drawable.ic_lap, false, 562 View.INVISIBLE); 563 setStartStopText(mCenterButton, R.string.sw_start_button); 564 showShareButton(false); 565 break; 566 case Stopwatches.STOPWATCH_RUNNING: 567 setButton(mLeftButton, R.string.sw_lap_button, R.drawable.ic_lap, 568 !reachedMaxLaps(), View.VISIBLE); 569 setStartStopText(mCenterButton, R.string.sw_stop_button); 570 showShareButton(false); 571 break; 572 case Stopwatches.STOPWATCH_STOPPED: 573 setButton(mLeftButton, R.string.sw_reset_button, R.drawable.ic_reset, true, 574 View.VISIBLE); 575 setStartStopText(mCenterButton, R.string.sw_start_button); 576 showShareButton(true); 577 break; 578 default: 579 break; 580 } 581 } reachedMaxLaps()582 private boolean reachedMaxLaps() { 583 return mLapsAdapter.getCount() >= Stopwatches.MAX_LAPS; 584 } 585 586 /*** 587 * Set a single button with the string and states provided. 588 * @param b - Button view to update 589 * @param text - Text in button 590 * @param enabled - enable/disables the button 591 * @param visibility - Show/hide the button 592 */ setButton( ImageButton b, int text, int drawableId, boolean enabled, int visibility)593 private void setButton( 594 ImageButton b, int text, int drawableId, boolean enabled, int visibility) { 595 b.setContentDescription(getActivity().getResources().getString(text)); 596 b.setImageResource(drawableId); 597 b.setVisibility(visibility); 598 b.setEnabled(enabled); 599 } 600 setStartStopText(TextView v, int text)601 private void setStartStopText(TextView v, int text) { 602 String textStr = getActivity().getResources().getString(text); 603 v.setText(textStr); 604 v.setContentDescription(textStr); 605 } 606 607 /*** 608 * 609 * @param time - in hundredths of a second 610 */ addLapTime(long time)611 private void addLapTime(long time) { 612 int size = mLapsAdapter.getCount(); 613 long curTime = time - mStartTime + mAccumulatedTime; 614 if (size == 0) { 615 // Always show the ending lap and a new one 616 Lap firstLap = new Lap(curTime, curTime); 617 mLapsAdapter.addLap(firstLap); 618 mLapsAdapter.addLap(new Lap(0, curTime)); 619 mTime.setIntervalTime(curTime); 620 mLapsAdapter.updateTimeFormats(firstLap); 621 } else { 622 long lapTime = curTime - ((Lap) mLapsAdapter.getItem(1)).mTotalTime; 623 ((Lap)mLapsAdapter.getItem(0)).mLapTime = lapTime; 624 ((Lap)mLapsAdapter.getItem(0)).mTotalTime = curTime; 625 mLapsAdapter.addLap(new Lap(0, 0)); 626 mTime.setMarkerTime(lapTime); 627 mLapsAdapter.updateLapFormat(); 628 // mTime.setIntervalTime(lapTime * 10); 629 } 630 mLapsAdapter.notifyDataSetChanged(); 631 // Start lap animation starting from the second lap 632 mTime.stopIntervalAnimation(); 633 if (!reachedMaxLaps()) { 634 mTime.startIntervalAnimation(); 635 } 636 } 637 updateCurrentLap(long totalTime)638 private void updateCurrentLap(long totalTime) { 639 if (mLapsAdapter.getCount() > 0) { 640 Lap curLap = (Lap)mLapsAdapter.getItem(0); 641 curLap.mLapTime = totalTime - ((Lap)mLapsAdapter.getItem(1)).mTotalTime; 642 curLap.mTotalTime = totalTime; 643 mLapsAdapter.notifyDataSetChanged(); 644 } 645 } 646 showLaps()647 private void showLaps() { 648 if (mLapsAdapter.getCount() > 0) { 649 mLapsList.setVisibility(View.VISIBLE); 650 } else { 651 mLapsList.setVisibility(View.INVISIBLE); 652 } 653 } 654 startUpdateThread()655 private void startUpdateThread() { 656 mTime.post(mTimeUpdateThread); 657 } 658 stopUpdateThread()659 private void stopUpdateThread() { 660 mTime.removeCallbacks(mTimeUpdateThread); 661 } 662 663 Runnable mTimeUpdateThread = new Runnable() { 664 @Override 665 public void run() { 666 long curTime = Utils.getTimeNow(); 667 long totalTime = mAccumulatedTime + (curTime - mStartTime); 668 if (mTime != null) { 669 mTimeText.setTime(totalTime, true, true); 670 } 671 if (mLapsAdapter.getCount() > 0) { 672 updateCurrentLap(totalTime); 673 } 674 mTime.postDelayed(mTimeUpdateThread, 10); 675 } 676 }; 677 writeToSharedPref(SharedPreferences prefs)678 private void writeToSharedPref(SharedPreferences prefs) { 679 SharedPreferences.Editor editor = prefs.edit(); 680 editor.putLong (Stopwatches.PREF_START_TIME, mStartTime); 681 editor.putLong (Stopwatches.PREF_ACCUM_TIME, mAccumulatedTime); 682 editor.putInt (Stopwatches.PREF_STATE, mState); 683 if (mLapsAdapter != null) { 684 long [] laps = mLapsAdapter.getLapTimes(); 685 if (laps != null) { 686 editor.putInt (Stopwatches.PREF_LAP_NUM, laps.length); 687 for (int i = 0; i < laps.length; i++) { 688 String key = Stopwatches.PREF_LAP_TIME + Integer.toString(laps.length - i); 689 editor.putLong (key, laps[i]); 690 } 691 } 692 } 693 if (mState == Stopwatches.STOPWATCH_RUNNING) { 694 editor.putLong(Stopwatches.NOTIF_CLOCK_BASE, mStartTime-mAccumulatedTime); 695 editor.putLong(Stopwatches.NOTIF_CLOCK_ELAPSED, -1); 696 editor.putBoolean(Stopwatches.NOTIF_CLOCK_RUNNING, true); 697 } else if (mState == Stopwatches.STOPWATCH_STOPPED) { 698 editor.putLong(Stopwatches.NOTIF_CLOCK_ELAPSED, mAccumulatedTime); 699 editor.putLong(Stopwatches.NOTIF_CLOCK_BASE, -1); 700 editor.putBoolean(Stopwatches.NOTIF_CLOCK_RUNNING, false); 701 } else if (mState == Stopwatches.STOPWATCH_RESET) { 702 editor.remove(Stopwatches.NOTIF_CLOCK_BASE); 703 editor.remove(Stopwatches.NOTIF_CLOCK_RUNNING); 704 editor.remove(Stopwatches.NOTIF_CLOCK_ELAPSED); 705 } 706 editor.putBoolean(Stopwatches.PREF_UPDATE_CIRCLE, false); 707 editor.apply(); 708 } 709 readFromSharedPref(SharedPreferences prefs)710 private void readFromSharedPref(SharedPreferences prefs) { 711 mStartTime = prefs.getLong(Stopwatches.PREF_START_TIME, 0); 712 mAccumulatedTime = prefs.getLong(Stopwatches.PREF_ACCUM_TIME, 0); 713 mState = prefs.getInt(Stopwatches.PREF_STATE, Stopwatches.STOPWATCH_RESET); 714 int numLaps = prefs.getInt(Stopwatches.PREF_LAP_NUM, Stopwatches.STOPWATCH_RESET); 715 if (mLapsAdapter != null) { 716 long[] oldLaps = mLapsAdapter.getLapTimes(); 717 if (oldLaps == null || oldLaps.length < numLaps) { 718 long[] laps = new long[numLaps]; 719 long prevLapElapsedTime = 0; 720 for (int lap_i = 0; lap_i < numLaps; lap_i++) { 721 String key = Stopwatches.PREF_LAP_TIME + Integer.toString(lap_i + 1); 722 long lap = prefs.getLong(key, 0); 723 laps[numLaps - lap_i - 1] = lap - prevLapElapsedTime; 724 prevLapElapsedTime = lap; 725 } 726 mLapsAdapter.setLapTimes(laps); 727 } 728 } 729 if (prefs.getBoolean(Stopwatches.PREF_UPDATE_CIRCLE, true)) { 730 if (mState == Stopwatches.STOPWATCH_STOPPED) { 731 doStop(); 732 } else if (mState == Stopwatches.STOPWATCH_RUNNING) { 733 doStart(mStartTime); 734 } else if (mState == Stopwatches.STOPWATCH_RESET) { 735 doReset(); 736 } 737 } 738 } 739 740 public class ImageLabelAdapter extends ArrayAdapter<CharSequence> { 741 private final ArrayList<CharSequence> mStrings; 742 private final ArrayList<Drawable> mDrawables; 743 private final ArrayList<String> mPackageNames; 744 private final ArrayList<String> mClassNames; 745 private ImageLabelAdapter mShowAllAdapter; 746 ImageLabelAdapter(Context context, int textViewResourceId, ArrayList<CharSequence> strings, ArrayList<Drawable> drawables, ArrayList<String> packageNames, ArrayList<String> classNames)747 public ImageLabelAdapter(Context context, int textViewResourceId, 748 ArrayList<CharSequence> strings, ArrayList<Drawable> drawables, 749 ArrayList<String> packageNames, ArrayList<String> classNames) { 750 super(context, textViewResourceId, strings); 751 mStrings = strings; 752 mDrawables = drawables; 753 mPackageNames = packageNames; 754 mClassNames = classNames; 755 } 756 757 // Use this constructor if showing a "see all" option, to pass in the adapter 758 // that will be needed to quickly show all the remaining options. ImageLabelAdapter(Context context, int textViewResourceId, ArrayList<CharSequence> strings, ArrayList<Drawable> drawables, ArrayList<String> packageNames, ArrayList<String> classNames, ImageLabelAdapter showAllAdapter)759 public ImageLabelAdapter(Context context, int textViewResourceId, 760 ArrayList<CharSequence> strings, ArrayList<Drawable> drawables, 761 ArrayList<String> packageNames, ArrayList<String> classNames, 762 ImageLabelAdapter showAllAdapter) { 763 super(context, textViewResourceId, strings); 764 mStrings = strings; 765 mDrawables = drawables; 766 mPackageNames = packageNames; 767 mClassNames = classNames; 768 mShowAllAdapter = showAllAdapter; 769 } 770 771 @Override getView(int position, View convertView, ViewGroup parent)772 public View getView(int position, View convertView, ViewGroup parent) { 773 LayoutInflater li = getActivity().getLayoutInflater(); 774 View row = li.inflate(R.layout.popup_window_item, parent, false); 775 ((TextView) row.findViewById(R.id.title)).setText( 776 mStrings.get(position)); 777 ((ImageView) row.findViewById(R.id.icon)).setBackground( 778 mDrawables.get(position)); 779 return row; 780 } 781 getPackageName(int position)782 public String getPackageName(int position) { 783 return mPackageNames.get(position); 784 } 785 getClassName(int position)786 public String getClassName(int position) { 787 return mClassNames.get(position); 788 } 789 getShowAllAdapter()790 public ImageLabelAdapter getShowAllAdapter() { 791 return mShowAllAdapter; 792 } 793 } 794 795 @Override onSharedPreferenceChanged(SharedPreferences prefs, String key)796 public void onSharedPreferenceChanged(SharedPreferences prefs, String key) { 797 if (prefs.equals(PreferenceManager.getDefaultSharedPreferences(getActivity()))) { 798 if (! (key.equals(Stopwatches.PREF_LAP_NUM) || 799 key.startsWith(Stopwatches.PREF_LAP_TIME))) { 800 readFromSharedPref(prefs); 801 if (prefs.getBoolean(Stopwatches.PREF_UPDATE_CIRCLE, true)) { 802 mTime.readFromSharedPref(prefs, "sw"); 803 } 804 } 805 } 806 } 807 808 // Used to keeps screen on when stopwatch is running. 809 acquireWakeLock()810 private void acquireWakeLock() { 811 if (mWakeLock == null) { 812 final PowerManager pm = 813 (PowerManager) getActivity().getSystemService(Context.POWER_SERVICE); 814 mWakeLock = pm.newWakeLock( 815 PowerManager.SCREEN_BRIGHT_WAKE_LOCK | PowerManager.ON_AFTER_RELEASE, TAG); 816 mWakeLock.setReferenceCounted(false); 817 } 818 mWakeLock.acquire(); 819 } 820 releaseWakeLock()821 private void releaseWakeLock() { 822 if (mWakeLock != null && mWakeLock.isHeld()) { 823 mWakeLock.release(); 824 } 825 } 826 827 } 828