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 41 public IBinder onBind(Intent intent) { 42 return null; 43 } 44 45 @Override 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 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 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 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. **/ 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. **/ 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 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 292 private void closeNotificationShade() { 293 Intent intent = new Intent(); 294 intent.setAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); 295 sendBroadcast(intent); 296 } 297 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 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 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 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 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 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 426 private void writeSharedPrefsReset(boolean updateCircle) { 427 writeToSharedPrefs(null, null, null, Stopwatches.STOPWATCH_RESET, updateCircle); 428 } 429 } 430