• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
onBind(Intent intent)38     public IBinder onBind(Intent intent) {
39         return null;
40     }
41 
42     @Override
onCreate()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
onStartCommand(Intent intent, int flags, int startId)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
onDestroy()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 
setNotification(long clockBaseTime, boolean clockRunning, int numLaps)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. **/
saveNotification(long clockTime, boolean clockRunning, int numLaps)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. **/
showSavedNotification()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 
clearSavedNotification()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 
closeNotificationShade()303     private void closeNotificationShade() {
304         Intent intent = new Intent();
305         intent.setAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
306         sendBroadcast(intent);
307     }
308 
readFromSharedPrefs()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 
readLapsFromPrefs()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 
writeToSharedPrefs(Long startTime, Long lapTimeElapsed, Long elapsedTime, Integer state, boolean updateCircle)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 
writeSharedPrefsStarted(long startTime, boolean updateCircle)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 
writeSharedPrefsLap(long lapTimeElapsed, boolean updateCircle)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 
writeSharedPrefsStopped(long elapsedTime, boolean updateCircle)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 
writeSharedPrefsReset(boolean updateCircle)437     private void writeSharedPrefsReset(boolean updateCircle) {
438         writeToSharedPrefs(null, null, null, Stopwatches.STOPWATCH_RESET, updateCircle);
439     }
440 }
441