• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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