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