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)
    249                 .setLocalOnly(true)
    250                 .build();
    251         notification.bigContentView = remoteViewsExpanded;
    252         mNotificationManager.notify(NOTIFICATION_ID, notification);
    253     }
    254 
    255     /** Save the notification to be shown when the app is closed. **/
    256     private void saveNotification(long clockTime, boolean clockRunning, int numLaps) {
    257         SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(
    258                 getApplicationContext());
    259         SharedPreferences.Editor editor = prefs.edit();
    260         if (clockRunning) {
    261             editor.putLong(Stopwatches.NOTIF_CLOCK_BASE, clockTime);
    262             editor.putLong(Stopwatches.NOTIF_CLOCK_ELAPSED, -1);
    263             editor.putBoolean(Stopwatches.NOTIF_CLOCK_RUNNING, true);
    264         } else {
    265             editor.putLong(Stopwatches.NOTIF_CLOCK_ELAPSED, clockTime);
    266             editor.putLong(Stopwatches.NOTIF_CLOCK_BASE, -1);
    267             editor.putBoolean(Stopwatches.NOTIF_CLOCK_RUNNING, false);
    268         }
    269         editor.putBoolean(Stopwatches.PREF_UPDATE_CIRCLE, false);
    270         editor.apply();
    271     }
    272 
    273     /** Show the most recently saved notification. **/
    274     private boolean showSavedNotification() {
    275         SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(
    276                 getApplicationContext());
    277         long clockBaseTime = prefs.getLong(Stopwatches.NOTIF_CLOCK_BASE, -1);
    278         long clockElapsedTime = prefs.getLong(Stopwatches.NOTIF_CLOCK_ELAPSED, -1);
    279         boolean clockRunning = prefs.getBoolean(Stopwatches.NOTIF_CLOCK_RUNNING, false);
    280         int numLaps = prefs.getInt(Stopwatches.PREF_LAP_NUM, -1);
    281         if (clockBaseTime == -1) {
    282             if (clockElapsedTime == -1) {
    283                 return false;
    284             } else {
    285                 // We don't have a clock base time, so the clock is stopped.
    286                 // Use the elapsed time to figure out what time to show.
    287                 mElapsedTime = clockElapsedTime;
    288                 clockBaseTime = Utils.getTimeNow() - clockElapsedTime;
    289             }
    290         }
    291         setNotification(clockBaseTime, clockRunning, numLaps);
    292         return true;
    293     }
    294 
    295     private void clearSavedNotification() {
    296         SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(
    297                 getApplicationContext());
    298         SharedPreferences.Editor editor = prefs.edit();
    299         editor.remove(Stopwatches.NOTIF_CLOCK_BASE);
    300         editor.remove(Stopwatches.NOTIF_CLOCK_RUNNING);
    301         editor.remove(Stopwatches.NOTIF_CLOCK_ELAPSED);
    302         editor.apply();
    303     }
    304 
    305     private void closeNotificationShade() {
    306         Intent intent = new Intent();
    307         intent.setAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
    308         sendBroadcast(intent);
    309     }
    310 
    311     private void readFromSharedPrefs() {
    312         SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(
    313                 getApplicationContext());
    314         mStartTime = prefs.getLong(Stopwatches.PREF_START_TIME, 0);
    315         mElapsedTime = prefs.getLong(Stopwatches.PREF_ACCUM_TIME, 0);
    316         mNumLaps = prefs.getInt(Stopwatches.PREF_LAP_NUM, Stopwatches.STOPWATCH_RESET);
    317     }
    318 
    319     private long[] readLapsFromPrefs() {
    320         SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(
    321                 getApplicationContext());
    322         int numLaps = prefs.getInt(Stopwatches.PREF_LAP_NUM, Stopwatches.STOPWATCH_RESET);
    323         long[] laps = new long[numLaps];
    324         long prevLapElapsedTime = 0;
    325         for (int lap_i = 0; lap_i < numLaps; lap_i++) {
    326             String key = Stopwatches.PREF_LAP_TIME + Integer.toString(lap_i + 1);
    327             long lap = prefs.getLong(key, 0);
    328             if (lap == prevLapElapsedTime && lap_i == numLaps - 1) {
    329                 lap = mElapsedTime;
    330             }
    331             laps[numLaps - lap_i - 1] = lap - prevLapElapsedTime;
    332             prevLapElapsedTime = lap;
    333         }
    334         return laps;
    335     }
    336 
    337     private void writeToSharedPrefs(Long startTime, Long lapTimeElapsed, Long elapsedTime,
    338             Integer state, boolean updateCircle) {
    339         SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(
    340                 getApplicationContext());
    341         SharedPreferences.Editor editor = prefs.edit();
    342         if (startTime != null) {
    343             editor.putLong(Stopwatches.PREF_START_TIME, startTime);
    344             mStartTime = startTime;
    345         }
    346         if (lapTimeElapsed != null) {
    347             int numLaps = prefs.getInt(Stopwatches.PREF_LAP_NUM, 0);
    348             if (numLaps == 0) {
    349                 mNumLaps++;
    350                 numLaps++;
    351             }
    352             editor.putLong(Stopwatches.PREF_LAP_TIME + Integer.toString(numLaps), lapTimeElapsed);
    353             numLaps++;
    354             editor.putLong(Stopwatches.PREF_LAP_TIME + Integer.toString(numLaps), lapTimeElapsed);
    355             editor.putInt(Stopwatches.PREF_LAP_NUM, numLaps);
    356         }
    357         if (elapsedTime != null) {
    358             editor.putLong(Stopwatches.PREF_ACCUM_TIME, elapsedTime);
    359             mElapsedTime = elapsedTime;
    360         }
    361         if (state != null) {
    362             if (state == Stopwatches.STOPWATCH_RESET) {
    363                 editor.putInt(Stopwatches.PREF_STATE, Stopwatches.STOPWATCH_RESET);
    364             } else if (state == Stopwatches.STOPWATCH_RUNNING) {
    365                 editor.putInt(Stopwatches.PREF_STATE, Stopwatches.STOPWATCH_RUNNING);
    366             } else if (state == Stopwatches.STOPWATCH_STOPPED) {
    367                 editor.putInt(Stopwatches.PREF_STATE, Stopwatches.STOPWATCH_STOPPED);
    368             }
    369         }
    370         editor.putBoolean(Stopwatches.PREF_UPDATE_CIRCLE, updateCircle);
    371         editor.apply();
    372     }
    373 
    374     private void writeSharedPrefsStarted(long startTime, boolean updateCircle) {
    375         writeToSharedPrefs(startTime, null, null, Stopwatches.STOPWATCH_RUNNING, updateCircle);
    376         if (updateCircle) {
    377             long time = Utils.getTimeNow();
    378             SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(
    379                     getApplicationContext());
    380             long intervalStartTime = prefs.getLong(
    381                     Stopwatches.KEY + CircleTimerView.PREF_CTV_INTERVAL_START, -1);
    382             if (intervalStartTime != -1) {
    383                 intervalStartTime = time;
    384                 SharedPreferences.Editor editor = prefs.edit();
    385                 editor.putLong(Stopwatches.KEY + CircleTimerView.PREF_CTV_INTERVAL_START,
    386                         intervalStartTime);
    387                 editor.putBoolean(Stopwatches.KEY + CircleTimerView.PREF_CTV_PAUSED, false);
    388                 editor.apply();
    389             }
    390         }
    391     }
    392 
    393     private void writeSharedPrefsLap(long lapTimeElapsed, boolean updateCircle) {
    394         writeToSharedPrefs(null, lapTimeElapsed, null, null, updateCircle);
    395         if (updateCircle) {
    396             long time = Utils.getTimeNow();
    397             SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(
    398                     getApplicationContext());
    399             SharedPreferences.Editor editor = prefs.edit();
    400             long laps[] = readLapsFromPrefs();
    401             int numLaps = laps.length;
    402             long lapTime = laps[1];
    403             if (numLaps == 2) { // Have only hit lap once.
    404                 editor.putLong(Stopwatches.KEY + CircleTimerView.PREF_CTV_INTERVAL, lapTime);
    405             } else {
    406                 editor.putLong(Stopwatches.KEY + CircleTimerView.PREF_CTV_MARKER_TIME, lapTime);
    407             }
    408             editor.putLong(Stopwatches.KEY + CircleTimerView.PREF_CTV_ACCUM_TIME, 0);
    409             if (numLaps < Stopwatches.MAX_LAPS) {
    410                 editor.putLong(Stopwatches.KEY + CircleTimerView.PREF_CTV_INTERVAL_START, time);
    411                 editor.putBoolean(Stopwatches.KEY + CircleTimerView.PREF_CTV_PAUSED, false);
    412             } else {
    413                 editor.putLong(Stopwatches.KEY + CircleTimerView.PREF_CTV_INTERVAL_START, -1);
    414             }
    415             editor.apply();
    416         }
    417     }
    418 
    419     private void writeSharedPrefsStopped(long elapsedTime, boolean updateCircle) {
    420         writeToSharedPrefs(null, null, elapsedTime, Stopwatches.STOPWATCH_STOPPED, updateCircle);
    421         if (updateCircle) {
    422             long time = Utils.getTimeNow();
    423             SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(
    424                     getApplicationContext());
    425             long accumulatedTime = prefs.getLong(
    426                     Stopwatches.KEY + CircleTimerView.PREF_CTV_ACCUM_TIME, 0);
    427             long intervalStartTime = prefs.getLong(
    428                     Stopwatches.KEY + CircleTimerView.PREF_CTV_INTERVAL_START, -1);
    429             accumulatedTime += time - intervalStartTime;
    430             SharedPreferences.Editor editor = prefs.edit();
    431             editor.putLong(Stopwatches.KEY + CircleTimerView.PREF_CTV_ACCUM_TIME, accumulatedTime);
    432             editor.putBoolean(Stopwatches.KEY + CircleTimerView.PREF_CTV_PAUSED, true);
    433             editor.putLong(
    434                     Stopwatches.KEY + CircleTimerView.PREF_CTV_CURRENT_INTERVAL, accumulatedTime);
    435             editor.apply();
    436         }
    437     }
    438 
    439     private void writeSharedPrefsReset(boolean updateCircle) {
    440         writeToSharedPrefs(null, null, null, Stopwatches.STOPWATCH_RESET, updateCircle);
    441     }
    442 }
    443