Home | History | Annotate | Download | only in stopwatch
      1 
      2 package com.android.deskclock.stopwatch;
      3 
      4 import android.app.Notification;
      5 import android.app.NotificationManager;
      6 import android.app.PendingIntent;
      7 import android.app.Service;
      8 import android.content.Context;
      9 import android.content.Intent;
     10 import android.content.SharedPreferences;
     11 import android.os.IBinder;
     12 import android.preference.PreferenceManager;
     13 import android.view.View;
     14 import android.widget.RemoteViews;
     15 
     16 import com.android.deskclock.CircleTimerView;
     17 import com.android.deskclock.DeskClock;
     18 import com.android.deskclock.R;
     19 import com.android.deskclock.Utils;
     20 
     21 /**
     22  * TODO: Insert description here. (generated by sblitz)
     23  */
     24 public class StopwatchService extends Service {
     25     // Member fields
     26     private int mNumLaps;
     27     private long mElapsedTime;
     28     private long mStartTime;
     29     private boolean mLoadApp;
     30     private NotificationManager mNotificationManager;
     31 
     32     // Constants for intent information
     33     // Make this a large number to avoid the alarm ID's which seem to be 1, 2, ...
     34     // Must also be different than TimerReceiver.IN_USE_NOTIFICATION_ID
     35     private static final int NOTIFICATION_ID = Integer.MAX_VALUE - 1;
     36 
     37     @Override
     38     public IBinder onBind(Intent intent) {
     39         return null;
     40     }
     41 
     42     @Override
     43     public void onCreate() {
     44         mNumLaps = 0;
     45         mElapsedTime = 0;
     46         mStartTime = 0;
     47         mLoadApp = false;
     48         mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
     49     }
     50 
     51     @Override
     52     public int onStartCommand(Intent intent, int flags, int startId) {
     53         if (intent == null) {
     54             return Service.START_NOT_STICKY;
     55         }
     56 
     57         if (mStartTime == 0 || mElapsedTime == 0 || mNumLaps == 0) {
     58             // May not have the most recent values.
     59             readFromSharedPrefs();
     60         }
     61 
     62         String actionType = intent.getAction();
     63         long actionTime = intent.getLongExtra(Stopwatches.MESSAGE_TIME, Utils.getTimeNow());
     64         boolean showNotif = intent.getBooleanExtra(Stopwatches.SHOW_NOTIF, true);
     65         boolean updateCircle = showNotif; // Don't save updates to the cirle if we're in the app.
     66         if (actionType.equals(Stopwatches.START_STOPWATCH)) {
     67             mStartTime = actionTime;
     68             writeSharedPrefsStarted(mStartTime, updateCircle);
     69             if (showNotif) {
     70                 setNotification(mStartTime - mElapsedTime, true, mNumLaps);
     71             } else {
     72                 saveNotification(mStartTime - mElapsedTime, true, mNumLaps);
     73             }
     74         } else if (actionType.equals(Stopwatches.LAP_STOPWATCH)) {
     75             mNumLaps++;
     76             long lapTimeElapsed = actionTime - mStartTime + mElapsedTime;
     77             writeSharedPrefsLap(lapTimeElapsed, updateCircle);
     78             if (showNotif) {
     79                 setNotification(mStartTime - mElapsedTime, true, mNumLaps);
     80             } else {
     81                 saveNotification(mStartTime - mElapsedTime, true, mNumLaps);
     82             }
     83         } else if (actionType.equals(Stopwatches.STOP_STOPWATCH)) {
     84             mElapsedTime = mElapsedTime + (actionTime - mStartTime);
     85             writeSharedPrefsStopped(mElapsedTime, updateCircle);
     86             if (showNotif) {
     87                 setNotification(actionTime - mElapsedTime, false, mNumLaps);
     88             } else {
     89                 saveNotification(mElapsedTime, false, mNumLaps);
     90             }
     91         } else if (actionType.equals(Stopwatches.RESET_STOPWATCH)) {
     92             mLoadApp = false;
     93             writeSharedPrefsReset(updateCircle);
     94             clearSavedNotification();
     95             stopSelf();
     96         } else if (actionType.equals(Stopwatches.RESET_AND_LAUNCH_STOPWATCH)) {
     97             mLoadApp = true;
     98             writeSharedPrefsReset(updateCircle);
     99             clearSavedNotification();
    100             closeNotificationShade();
    101             stopSelf();
    102         } else if (actionType.equals(Stopwatches.SHARE_STOPWATCH)) {
    103             closeNotificationShade();
    104             Intent shareIntent = new Intent(android.content.Intent.ACTION_SEND);
    105             shareIntent.setType("text/plain");
    106             shareIntent.putExtra(
    107                     Intent.EXTRA_SUBJECT, Stopwatches.getShareTitle(getApplicationContext()));
    108             shareIntent.putExtra(Intent.EXTRA_TEXT, Stopwatches.buildShareResults(
    109                     getApplicationContext(), mElapsedTime, readLapsFromPrefs()));
    110             Intent chooserIntent = Intent.createChooser(shareIntent, null);
    111             chooserIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    112             getApplication().startActivity(chooserIntent);
    113         } else if (actionType.equals(Stopwatches.SHOW_NOTIF)) {
    114             // SHOW_NOTIF sent from the DeskClock.onPause
    115             // If a notification is not displayed, this service's work is over
    116             if (!showSavedNotification()) {
    117                 stopSelf();
    118             }
    119         } else if (actionType.equals(Stopwatches.KILL_NOTIF)) {
    120             mNotificationManager.cancel(NOTIFICATION_ID);
    121         }
    122 
    123         // We want this service to continue running until it is explicitly
    124         // stopped, so return sticky.
    125         return START_STICKY;
    126     }
    127 
    128     @Override
    129     public void onDestroy() {
    130         mNotificationManager.cancel(NOTIFICATION_ID);
    131         clearSavedNotification();
    132         mNumLaps = 0;
    133         mElapsedTime = 0;
    134         mStartTime = 0;
    135         if (mLoadApp) {
    136             Intent activityIntent = new Intent(getApplicationContext(), DeskClock.class);
    137             activityIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    138             activityIntent.putExtra(
    139                     DeskClock.SELECT_TAB_INTENT_EXTRA, DeskClock.STOPWATCH_TAB_INDEX);
    140             startActivity(activityIntent);
    141             mLoadApp = false;
    142         }
    143     }
    144 
    145     private void setNotification(long clockBaseTime, boolean clockRunning, int numLaps) {
    146         Context context = getApplicationContext();
    147         // Intent to load the app for a non-button click.
    148         Intent intent = new Intent(context, DeskClock.class);
    149         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    150         intent.putExtra(DeskClock.SELECT_TAB_INTENT_EXTRA, DeskClock.STOPWATCH_TAB_INDEX);
    151         // add category to distinguish between stopwatch intents and timer intents
    152         intent.addCategory("stopwatch");
    153         PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent,
    154                 PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_UPDATE_CURRENT);
    155 
    156         // Set up remoteviews for the notification.
    157         RemoteViews remoteViewsCollapsed = new RemoteViews(getPackageName(),
    158                 R.layout.stopwatch_notif_collapsed);
    159         remoteViewsCollapsed.setOnClickPendingIntent(R.id.swn_collapsed_hitspace, pendingIntent);
    160         remoteViewsCollapsed.setChronometer(
    161                 R.id.swn_collapsed_chronometer, clockBaseTime, null, clockRunning);
    162         remoteViewsCollapsed.
    163                 setImageViewResource(R.id.notification_icon, R.drawable.stat_notify_stopwatch);
    164         RemoteViews remoteViewsExpanded = new RemoteViews(getPackageName(),
    165                 R.layout.stopwatch_notif_expanded);
    166         remoteViewsExpanded.setOnClickPendingIntent(R.id.swn_expanded_hitspace, pendingIntent);
    167         remoteViewsExpanded.setChronometer(
    168                 R.id.swn_expanded_chronometer, clockBaseTime, null, clockRunning);
    169         remoteViewsExpanded.
    170                 setImageViewResource(R.id.notification_icon, R.drawable.stat_notify_stopwatch);
    171 
    172         if (clockRunning) {
    173             // Left button: lap
    174             remoteViewsExpanded.setTextViewText(
    175                     R.id.swn_left_button, getResources().getText(R.string.sw_lap_button));
    176             Intent leftButtonIntent = new Intent(context, StopwatchService.class);
    177             leftButtonIntent.setAction(Stopwatches.LAP_STOPWATCH);
    178             remoteViewsExpanded.setOnClickPendingIntent(R.id.swn_left_button,
    179                     PendingIntent.getService(context, 0, leftButtonIntent, 0));
    180             remoteViewsExpanded.
    181                     setTextViewCompoundDrawablesRelative(R.id.swn_left_button,
    182                             R.drawable.ic_notify_lap, 0, 0, 0);
    183 
    184             // Right button: stop clock
    185             remoteViewsExpanded.setTextViewText(
    186                     R.id.swn_right_button, getResources().getText(R.string.sw_stop_button));
    187             Intent rightButtonIntent = new Intent(context, StopwatchService.class);
    188             rightButtonIntent.setAction(Stopwatches.STOP_STOPWATCH);
    189             remoteViewsExpanded.setOnClickPendingIntent(R.id.swn_right_button,
    190                     PendingIntent.getService(context, 0, rightButtonIntent, 0));
    191             remoteViewsExpanded.
    192                     setTextViewCompoundDrawablesRelative(R.id.swn_right_button,
    193                             R.drawable.ic_notify_stop, 0, 0, 0);
    194 
    195             // Show the laps if applicable.
    196             if (numLaps > 0) {
    197                 String lapText = String.format(
    198                         context.getString(R.string.sw_notification_lap_number), numLaps);
    199                 remoteViewsCollapsed.setTextViewText(R.id.swn_collapsed_laps, lapText);
    200                 remoteViewsCollapsed.setViewVisibility(R.id.swn_collapsed_laps, View.VISIBLE);
    201                 remoteViewsExpanded.setTextViewText(R.id.swn_expanded_laps, lapText);
    202                 remoteViewsExpanded.setViewVisibility(R.id.swn_expanded_laps, View.VISIBLE);
    203             } else {
    204                 remoteViewsCollapsed.setViewVisibility(R.id.swn_collapsed_laps, View.GONE);
    205                 remoteViewsExpanded.setViewVisibility(R.id.swn_expanded_laps, View.GONE);
    206             }
    207         } else {
    208             // Left button: reset clock
    209             remoteViewsExpanded.setTextViewText(
    210                     R.id.swn_left_button, getResources().getText(R.string.sw_reset_button));
    211             Intent leftButtonIntent = new Intent(context, StopwatchService.class);
    212             leftButtonIntent.setAction(Stopwatches.RESET_AND_LAUNCH_STOPWATCH);
    213             remoteViewsExpanded.setOnClickPendingIntent(R.id.swn_left_button,
    214                     PendingIntent.getService(context, 0, leftButtonIntent, 0));
    215             remoteViewsExpanded.
    216                     setTextViewCompoundDrawablesRelative(R.id.swn_left_button,
    217                             R.drawable.ic_notify_reset, 0, 0, 0);
    218 
    219             // Right button: start clock
    220             remoteViewsExpanded.setTextViewText(
    221                     R.id.swn_right_button, getResources().getText(R.string.sw_start_button));
    222             Intent rightButtonIntent = new Intent(context, StopwatchService.class);
    223             rightButtonIntent.setAction(Stopwatches.START_STOPWATCH);
    224             remoteViewsExpanded.setOnClickPendingIntent(R.id.swn_right_button,
    225                     PendingIntent.getService(context, 0, rightButtonIntent, 0));
    226             remoteViewsExpanded.
    227                     setTextViewCompoundDrawablesRelative(R.id.swn_right_button,
    228                             R.drawable.ic_notify_start, 0, 0, 0);
    229 
    230             // Show stopped string.
    231             remoteViewsCollapsed.
    232                     setTextViewText(R.id.swn_collapsed_laps, getString(R.string.swn_stopped));
    233             remoteViewsCollapsed.setViewVisibility(R.id.swn_collapsed_laps, View.VISIBLE);
    234             remoteViewsExpanded.
    235                     setTextViewText(R.id.swn_expanded_laps, getString(R.string.swn_stopped));
    236             remoteViewsExpanded.setViewVisibility(R.id.swn_expanded_laps, View.VISIBLE);
    237         }
    238 
    239         Intent dismissIntent = new Intent(context, StopwatchService.class);
    240         dismissIntent.setAction(Stopwatches.RESET_STOPWATCH);
    241 
    242         Notification notification = new Notification.Builder(context)
    243                 .setAutoCancel(!clockRunning)
    244                 .setContent(remoteViewsCollapsed)
    245                 .setOngoing(clockRunning)
    246                 .setDeleteIntent(PendingIntent.getService(context, 0, dismissIntent, 0))
    247                 .setSmallIcon(R.drawable.ic_tab_stopwatch_activated)
    248                 .setPriority(Notification.PRIORITY_MAX).build();
    249         notification.bigContentView = remoteViewsExpanded;
    250         mNotificationManager.notify(NOTIFICATION_ID, notification);
    251     }
    252 
    253     /** Save the notification to be shown when the app is closed. **/
    254     private void saveNotification(long clockTime, boolean clockRunning, int numLaps) {
    255         SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(
    256                 getApplicationContext());
    257         SharedPreferences.Editor editor = prefs.edit();
    258         if (clockRunning) {
    259             editor.putLong(Stopwatches.NOTIF_CLOCK_BASE, clockTime);
    260             editor.putLong(Stopwatches.NOTIF_CLOCK_ELAPSED, -1);
    261             editor.putBoolean(Stopwatches.NOTIF_CLOCK_RUNNING, true);
    262         } else {
    263             editor.putLong(Stopwatches.NOTIF_CLOCK_ELAPSED, clockTime);
    264             editor.putLong(Stopwatches.NOTIF_CLOCK_BASE, -1);
    265             editor.putBoolean(Stopwatches.NOTIF_CLOCK_RUNNING, false);
    266         }
    267         editor.putBoolean(Stopwatches.PREF_UPDATE_CIRCLE, false);
    268         editor.apply();
    269     }
    270 
    271     /** Show the most recently saved notification. **/
    272     private boolean showSavedNotification() {
    273         SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(
    274                 getApplicationContext());
    275         long clockBaseTime = prefs.getLong(Stopwatches.NOTIF_CLOCK_BASE, -1);
    276         long clockElapsedTime = prefs.getLong(Stopwatches.NOTIF_CLOCK_ELAPSED, -1);
    277         boolean clockRunning = prefs.getBoolean(Stopwatches.NOTIF_CLOCK_RUNNING, false);
    278         int numLaps = prefs.getInt(Stopwatches.PREF_LAP_NUM, -1);
    279         if (clockBaseTime == -1) {
    280             if (clockElapsedTime == -1) {
    281                 return false;
    282             } else {
    283                 // We don't have a clock base time, so the clock is stopped.
    284                 // Use the elapsed time to figure out what time to show.
    285                 mElapsedTime = clockElapsedTime;
    286                 clockBaseTime = Utils.getTimeNow() - clockElapsedTime;
    287             }
    288         }
    289         setNotification(clockBaseTime, clockRunning, numLaps);
    290         return true;
    291     }
    292 
    293     private void clearSavedNotification() {
    294         SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(
    295                 getApplicationContext());
    296         SharedPreferences.Editor editor = prefs.edit();
    297         editor.remove(Stopwatches.NOTIF_CLOCK_BASE);
    298         editor.remove(Stopwatches.NOTIF_CLOCK_RUNNING);
    299         editor.remove(Stopwatches.NOTIF_CLOCK_ELAPSED);
    300         editor.apply();
    301     }
    302 
    303     private void closeNotificationShade() {
    304         Intent intent = new Intent();
    305         intent.setAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
    306         sendBroadcast(intent);
    307     }
    308 
    309     private void readFromSharedPrefs() {
    310         SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(
    311                 getApplicationContext());
    312         mStartTime = prefs.getLong(Stopwatches.PREF_START_TIME, 0);
    313         mElapsedTime = prefs.getLong(Stopwatches.PREF_ACCUM_TIME, 0);
    314         mNumLaps = prefs.getInt(Stopwatches.PREF_LAP_NUM, Stopwatches.STOPWATCH_RESET);
    315     }
    316 
    317     private long[] readLapsFromPrefs() {
    318         SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(
    319                 getApplicationContext());
    320         int numLaps = prefs.getInt(Stopwatches.PREF_LAP_NUM, Stopwatches.STOPWATCH_RESET);
    321         long[] laps = new long[numLaps];
    322         long prevLapElapsedTime = 0;
    323         for (int lap_i = 0; lap_i < numLaps; lap_i++) {
    324             String key = Stopwatches.PREF_LAP_TIME + Integer.toString(lap_i + 1);
    325             long lap = prefs.getLong(key, 0);
    326             if (lap == prevLapElapsedTime && lap_i == numLaps - 1) {
    327                 lap = mElapsedTime;
    328             }
    329             laps[numLaps - lap_i - 1] = lap - prevLapElapsedTime;
    330             prevLapElapsedTime = lap;
    331         }
    332         return laps;
    333     }
    334 
    335     private void writeToSharedPrefs(Long startTime, Long lapTimeElapsed, Long elapsedTime,
    336             Integer state, boolean updateCircle) {
    337         SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(
    338                 getApplicationContext());
    339         SharedPreferences.Editor editor = prefs.edit();
    340         if (startTime != null) {
    341             editor.putLong(Stopwatches.PREF_START_TIME, startTime);
    342             mStartTime = startTime;
    343         }
    344         if (lapTimeElapsed != null) {
    345             int numLaps = prefs.getInt(Stopwatches.PREF_LAP_NUM, 0);
    346             if (numLaps == 0) {
    347                 mNumLaps++;
    348                 numLaps++;
    349             }
    350             editor.putLong(Stopwatches.PREF_LAP_TIME + Integer.toString(numLaps), lapTimeElapsed);
    351             numLaps++;
    352             editor.putLong(Stopwatches.PREF_LAP_TIME + Integer.toString(numLaps), lapTimeElapsed);
    353             editor.putInt(Stopwatches.PREF_LAP_NUM, numLaps);
    354         }
    355         if (elapsedTime != null) {
    356             editor.putLong(Stopwatches.PREF_ACCUM_TIME, elapsedTime);
    357             mElapsedTime = elapsedTime;
    358         }
    359         if (state != null) {
    360             if (state == Stopwatches.STOPWATCH_RESET) {
    361                 editor.putInt(Stopwatches.PREF_STATE, Stopwatches.STOPWATCH_RESET);
    362             } else if (state == Stopwatches.STOPWATCH_RUNNING) {
    363                 editor.putInt(Stopwatches.PREF_STATE, Stopwatches.STOPWATCH_RUNNING);
    364             } else if (state == Stopwatches.STOPWATCH_STOPPED) {
    365                 editor.putInt(Stopwatches.PREF_STATE, Stopwatches.STOPWATCH_STOPPED);
    366             }
    367         }
    368         editor.putBoolean(Stopwatches.PREF_UPDATE_CIRCLE, updateCircle);
    369         editor.apply();
    370     }
    371 
    372     private void writeSharedPrefsStarted(long startTime, boolean updateCircle) {
    373         writeToSharedPrefs(startTime, null, null, Stopwatches.STOPWATCH_RUNNING, updateCircle);
    374         if (updateCircle) {
    375             long time = Utils.getTimeNow();
    376             SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(
    377                     getApplicationContext());
    378             long intervalStartTime = prefs.getLong(
    379                     Stopwatches.KEY + CircleTimerView.PREF_CTV_INTERVAL_START, -1);
    380             if (intervalStartTime != -1) {
    381                 intervalStartTime = time;
    382                 SharedPreferences.Editor editor = prefs.edit();
    383                 editor.putLong(Stopwatches.KEY + CircleTimerView.PREF_CTV_INTERVAL_START,
    384                         intervalStartTime);
    385                 editor.putBoolean(Stopwatches.KEY + CircleTimerView.PREF_CTV_PAUSED, false);
    386                 editor.apply();
    387             }
    388         }
    389     }
    390 
    391     private void writeSharedPrefsLap(long lapTimeElapsed, boolean updateCircle) {
    392         writeToSharedPrefs(null, lapTimeElapsed, null, null, updateCircle);
    393         if (updateCircle) {
    394             long time = Utils.getTimeNow();
    395             SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(
    396                     getApplicationContext());
    397             SharedPreferences.Editor editor = prefs.edit();
    398             long laps[] = readLapsFromPrefs();
    399             int numLaps = laps.length;
    400             long lapTime = laps[1];
    401             if (numLaps == 2) { // Have only hit lap once.
    402                 editor.putLong(Stopwatches.KEY + CircleTimerView.PREF_CTV_INTERVAL, lapTime);
    403             } else {
    404                 editor.putLong(Stopwatches.KEY + CircleTimerView.PREF_CTV_MARKER_TIME, lapTime);
    405             }
    406             editor.putLong(Stopwatches.KEY + CircleTimerView.PREF_CTV_ACCUM_TIME, 0);
    407             if (numLaps < Stopwatches.MAX_LAPS) {
    408                 editor.putLong(Stopwatches.KEY + CircleTimerView.PREF_CTV_INTERVAL_START, time);
    409                 editor.putBoolean(Stopwatches.KEY + CircleTimerView.PREF_CTV_PAUSED, false);
    410             } else {
    411                 editor.putLong(Stopwatches.KEY + CircleTimerView.PREF_CTV_INTERVAL_START, -1);
    412             }
    413             editor.apply();
    414         }
    415     }
    416 
    417     private void writeSharedPrefsStopped(long elapsedTime, boolean updateCircle) {
    418         writeToSharedPrefs(null, null, elapsedTime, Stopwatches.STOPWATCH_STOPPED, updateCircle);
    419         if (updateCircle) {
    420             long time = Utils.getTimeNow();
    421             SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(
    422                     getApplicationContext());
    423             long accumulatedTime = prefs.getLong(
    424                     Stopwatches.KEY + CircleTimerView.PREF_CTV_ACCUM_TIME, 0);
    425             long intervalStartTime = prefs.getLong(
    426                     Stopwatches.KEY + CircleTimerView.PREF_CTV_INTERVAL_START, -1);
    427             accumulatedTime += time - intervalStartTime;
    428             SharedPreferences.Editor editor = prefs.edit();
    429             editor.putLong(Stopwatches.KEY + CircleTimerView.PREF_CTV_ACCUM_TIME, accumulatedTime);
    430             editor.putBoolean(Stopwatches.KEY + CircleTimerView.PREF_CTV_PAUSED, true);
    431             editor.putLong(
    432                     Stopwatches.KEY + CircleTimerView.PREF_CTV_CURRENT_INTERVAL, accumulatedTime);
    433             editor.apply();
    434         }
    435     }
    436 
    437     private void writeSharedPrefsReset(boolean updateCircle) {
    438         writeToSharedPrefs(null, null, null, Stopwatches.STOPWATCH_RESET, updateCircle);
    439     }
    440 }
    441