• 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         // 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
onPause()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
onPageChanged(int page)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 
doStop()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 
doStart(long time)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 
doLap()424     private void doLap() {
425         showLaps();
426         setButtons(Stopwatches.STOPWATCH_RUNNING);
427     }
428 
doReset()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 
showShareButton(boolean show)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 
showSharePopup()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 
getShareIntent()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. **/
getLapShareTimes(long[] input)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      */
setButtons(int state)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     }
reachedMaxLaps()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      */
setButton( ImageButton b, int text, int drawableId, boolean enabled, int visibility)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 
setStartStopText(TextView v, int text)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      */
addLapTime(long time)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 
updateCurrentLap(long totalTime)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 
showLaps()660     private void showLaps() {
661         if (mLapsAdapter.getCount() > 0) {
662             mLapsList.setVisibility(View.VISIBLE);
663         } else {
664             mLapsList.setVisibility(View.INVISIBLE);
665         }
666     }
667 
startUpdateThread()668     private void startUpdateThread() {
669         mTime.post(mTimeUpdateThread);
670     }
671 
stopUpdateThread()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 
writeToSharedPref(SharedPreferences prefs)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 
readFromSharedPref(SharedPreferences prefs)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 
ImageLabelAdapter(Context context, int textViewResourceId, ArrayList<CharSequence> strings, ArrayList<Drawable> drawables, ArrayList<String> packageNames, ArrayList<String> classNames)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.
ImageLabelAdapter(Context context, int textViewResourceId, ArrayList<CharSequence> strings, ArrayList<Drawable> drawables, ArrayList<String> packageNames, ArrayList<String> classNames, ImageLabelAdapter showAllAdapter)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
getView(int position, View convertView, ViewGroup parent)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 
getPackageName(int position)795         public String getPackageName(int position) {
796             return mPackageNames.get(position);
797         }
798 
getClassName(int position)799         public String getClassName(int position) {
800             return mClassNames.get(position);
801         }
802 
getShowAllAdapter()803         public ImageLabelAdapter getShowAllAdapter() {
804             return mShowAllAdapter;
805         }
806     }
807 
808     @Override
onSharedPreferenceChanged(SharedPreferences prefs, String key)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 
acquireWakeLock()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 
releaseWakeLock()834     private void releaseWakeLock() {
835         if (mWakeLock != null && mWakeLock.isHeld()) {
836             mWakeLock.release();
837         }
838     }
839 
840 }
841