1 2package com.android.deskclock.stopwatch; 3 4import android.app.Notification; 5import android.app.NotificationManager; 6import android.app.PendingIntent; 7import android.app.Service; 8import android.content.Context; 9import android.content.Intent; 10import android.content.SharedPreferences; 11import android.os.IBinder; 12import android.preference.PreferenceManager; 13import android.view.View; 14import android.widget.RemoteViews; 15 16import com.android.deskclock.CircleTimerView; 17import com.android.deskclock.DeskClock; 18import com.android.deskclock.R; 19import com.android.deskclock.Utils; 20 21/** 22 * TODO: Insert description here. (generated by sblitz) 23 */ 24public 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).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. **/ 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. **/ 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 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 303 private void closeNotificationShade() { 304 Intent intent = new Intent(); 305 intent.setAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); 306 sendBroadcast(intent); 307 } 308 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 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 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 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 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 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 437 private void writeSharedPrefsReset(boolean updateCircle) { 438 writeToSharedPrefs(null, null, null, Stopwatches.STOPWATCH_RESET, updateCircle); 439 } 440} 441