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