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