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