• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
onBind(Intent intent)41     public IBinder onBind(Intent intent) {
42         return null;
43     }
44 
45     @Override
onCreate()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
onStartCommand(Intent intent, int flags, int startId)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
onDestroy()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 
setNotification(long clockBaseTime, boolean clockRunning, int numLaps)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. **/
saveNotification(long clockTime, boolean clockRunning, int numLaps)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. **/
showSavedNotification()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 
clearSavedNotification()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 
closeNotificationShade()292     private void closeNotificationShade() {
293         Intent intent = new Intent();
294         intent.setAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
295         sendBroadcast(intent);
296     }
297 
readFromSharedPrefs()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 
readLapsFromPrefs()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 
writeToSharedPrefs(Long startTime, Long lapTimeElapsed, Long elapsedTime, Integer state, boolean updateCircle)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 
writeSharedPrefsStarted(long startTime, boolean updateCircle)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 
writeSharedPrefsLap(long lapTimeElapsed, boolean updateCircle)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 
writeSharedPrefsStopped(long elapsedTime, boolean updateCircle)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 
writeSharedPrefsReset(boolean updateCircle)426     private void writeSharedPrefsReset(boolean updateCircle) {
427         writeToSharedPrefs(null, null, null, Stopwatches.STOPWATCH_RESET, updateCircle);
428     }
429 }
430