Moved lap inserting and updating to StopwatchNotificationService
This commit is contained in:
parent
ad7335c6d6
commit
facdf05602
@ -1,6 +1,7 @@
|
||||
package com.philliphsu.clock2.stopwatch;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
||||
import com.philliphsu.clock2.AsyncDatabaseTableUpdateHandler;
|
||||
import com.philliphsu.clock2.alarms.ScrollHandler;
|
||||
@ -19,6 +20,17 @@ public class AsyncLapsTableUpdateHandler extends AsyncDatabaseTableUpdateHandler
|
||||
return new LapsTableManager(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostAsyncInsert(Long result, Lap item) {
|
||||
if (result > 1) {
|
||||
// Update the notification's title with this lap number
|
||||
Intent intent = new Intent(getContext(), StopwatchNotificationService.class)
|
||||
.setAction(StopwatchNotificationService.ACTION_UPDATE_LAP_TITLE)
|
||||
.putExtra(StopwatchNotificationService.EXTRA_LAP_NUMBER, result.intValue());
|
||||
getContext().startService(intent);
|
||||
}
|
||||
}
|
||||
|
||||
// ===================== DO NOT IMPLEMENT =========================
|
||||
|
||||
@Override
|
||||
@ -26,11 +38,6 @@ public class AsyncLapsTableUpdateHandler extends AsyncDatabaseTableUpdateHandler
|
||||
// Leave blank.
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostAsyncInsert(Long result, Lap item) {
|
||||
// Leave blank.
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostAsyncUpdate(Long result, Lap item) {
|
||||
// Leave blank.
|
||||
|
||||
@ -46,10 +46,7 @@ public class StopwatchFragment extends RecyclerViewFragment<
|
||||
// TODO: See if we can remove these? Since we save prefs in the notif. service.
|
||||
private long mStartTime;
|
||||
private long mPauseTime;
|
||||
private Lap mCurrentLap;
|
||||
private Lap mPreviousLap;
|
||||
|
||||
private AsyncLapsTableUpdateHandler mUpdateHandler;
|
||||
private ObjectAnimator mProgressAnimator;
|
||||
private SharedPreferences mPrefs;
|
||||
private WeakReference<FloatingActionButton> mActivityFab;
|
||||
@ -69,7 +66,6 @@ public class StopwatchFragment extends RecyclerViewFragment<
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
mUpdateHandler = new AsyncLapsTableUpdateHandler(getActivity(), null/*we shouldn't need a scroll handler*/);
|
||||
mPrefs = PreferenceManager.getDefaultSharedPreferences(getActivity());
|
||||
mStartTime = mPrefs.getLong(KEY_START_TIME, 0);
|
||||
mPauseTime = mPrefs.getLong(KEY_PAUSE_TIME, 0);
|
||||
@ -79,6 +75,8 @@ public class StopwatchFragment extends RecyclerViewFragment<
|
||||
// onCreateView() or any other callback that is guaranteed to be called.
|
||||
mStartDrawable = ContextCompat.getDrawable(getActivity(), R.drawable.ic_start_24dp);
|
||||
mPauseDrawable = ContextCompat.getDrawable(getActivity(), R.drawable.ic_pause_24dp);
|
||||
|
||||
// TODO: Load the current lap here
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@ -184,15 +182,17 @@ public class StopwatchFragment extends RecyclerViewFragment<
|
||||
// TODO: Will manipulating the cursor's position here affect the current
|
||||
// position in the adapter? Should we make a defensive copy and manipulate
|
||||
// that copy instead?
|
||||
Lap currentLap = null;
|
||||
Lap previousLap = null;
|
||||
if (data.moveToFirst()) {
|
||||
mCurrentLap = data.getItem();
|
||||
currentLap = data.getItem();
|
||||
// Log.d(TAG, "Current lap ID = " + mCurrentLap.getId());
|
||||
}
|
||||
if (data.moveToNext()) {
|
||||
mPreviousLap = data.getItem();
|
||||
previousLap = data.getItem();
|
||||
// Log.d(TAG, "Previous lap ID = " + mPreviousLap.getId());
|
||||
}
|
||||
if (mCurrentLap != null && mPreviousLap != null) {
|
||||
if (currentLap != null && previousLap != null) {
|
||||
// We really only want to start a new animator when the NEWLY RETRIEVED current
|
||||
// and previous laps are different (i.e. different laps, NOT merely different instances)
|
||||
// from the CURRENT current and previous laps, as referenced by mCurrentLap and mPreviousLap.
|
||||
@ -208,13 +208,13 @@ public class StopwatchFragment extends RecyclerViewFragment<
|
||||
// NOTE: If we just recreated ourselves due to rotation, mChronometer.isRunning() == false,
|
||||
// because it is not yet visible (i.e. mVisible == false).
|
||||
if (isStopwatchRunning()) {
|
||||
startNewProgressBarAnimator();
|
||||
startNewProgressBarAnimator(currentLap, previousLap);
|
||||
} else {
|
||||
// I verified the bar was visible already without this, so we probably don't need this,
|
||||
// but it's just a safety measure..
|
||||
// ACTUALLY NOT A SAFETY MEASURE!
|
||||
// mSeekBar.setVisibility(View.VISIBLE);
|
||||
ProgressBarUtils.setProgress(mSeekBar, getCurrentLapProgressRatio());
|
||||
ProgressBarUtils.setProgress(mSeekBar, getCurrentLapProgressRatio(currentLap, previousLap));
|
||||
}
|
||||
} else {
|
||||
mSeekBar.setVisibility(View.INVISIBLE);
|
||||
@ -228,10 +228,6 @@ public class StopwatchFragment extends RecyclerViewFragment<
|
||||
|
||||
final Intent serviceIntent = new Intent(getActivity(), StopwatchNotificationService.class);
|
||||
if (mStartTime == 0) {
|
||||
// addNewLap() won't call through unless chronometer is running, which
|
||||
// we can't start until we compute mStartTime
|
||||
mCurrentLap = new Lap();
|
||||
mUpdateHandler.asyncInsert(mCurrentLap);
|
||||
setMiniFabsVisible(true);
|
||||
// Handle the default action, i.e. post the notification for the first time.
|
||||
getActivity().startService(serviceIntent);
|
||||
@ -258,41 +254,14 @@ public class StopwatchFragment extends RecyclerViewFragment<
|
||||
|
||||
@OnClick(R.id.new_lap)
|
||||
void addNewLap() {
|
||||
if (!mChronometer.isRunning()) {
|
||||
Log.d(TAG, "Cannot add new lap");
|
||||
return;
|
||||
}
|
||||
if (mCurrentLap != null) {
|
||||
mCurrentLap.end(mChronometer.getText().toString());
|
||||
}
|
||||
mPreviousLap = mCurrentLap;
|
||||
mCurrentLap = new Lap();
|
||||
if (mPreviousLap != null) {
|
||||
// if (getAdapter().getItemCount() == 0) {
|
||||
// mUpdateHandler.asyncInsert(mPreviousLap);
|
||||
// } else {
|
||||
mUpdateHandler.asyncUpdate(mPreviousLap.getId(), mPreviousLap);
|
||||
// }
|
||||
}
|
||||
mUpdateHandler.asyncInsert(mCurrentLap);
|
||||
// This would end up being called twice: here, and in onLoadFinished(), because the
|
||||
// table updates will prompt us to requery.
|
||||
// startNewProgressBarAnimator();
|
||||
// TODO: Start service with ACTION_ADD_LAP
|
||||
Intent serviceIntent = new Intent(getActivity(), StopwatchNotificationService.class)
|
||||
.setAction(StopwatchNotificationService.ACTION_ADD_LAP);
|
||||
getActivity().startService(serviceIntent);
|
||||
}
|
||||
|
||||
@OnClick(R.id.stop)
|
||||
void stop() {
|
||||
// Remove the notification. This will also write to prefs and clear the laps table.
|
||||
//
|
||||
// The service will make changes to shared prefs, which will fire our
|
||||
// OnSharedPrefChangeListener, which will call this stop() method. As such, this
|
||||
// stop() method will be called twice: first from the click, and the second from
|
||||
// the OnSharedPrefChange callback.
|
||||
// TODO: Make similar changes as you did with onFabClick() so that the above method calls
|
||||
// are made in the OnSharedPrefChange callback. You may find it helpful to extract
|
||||
// the above method calls into private helper methods, just as you did for
|
||||
// onFabClick().
|
||||
Intent stop = new Intent(getActivity(), StopwatchNotificationService.class)
|
||||
.setAction(StopwatchNotificationService.ACTION_STOP);
|
||||
getActivity().startService(stop);
|
||||
@ -311,8 +280,6 @@ public class StopwatchFragment extends RecyclerViewFragment<
|
||||
// stop() will also make a call to updateRunning(), but the running state has not
|
||||
// changed from the time we left the app.
|
||||
mChronometer.stop();
|
||||
mCurrentLap.pause();
|
||||
mUpdateHandler.asyncUpdate(mCurrentLap.getId(), mCurrentLap);
|
||||
// No issues controlling the animator here, because onLoadFinished() can't
|
||||
// call through to startNewProgressBarAnimator(), because by that point
|
||||
// the chronometer won't be running.
|
||||
@ -330,10 +297,6 @@ public class StopwatchFragment extends RecyclerViewFragment<
|
||||
mPauseTime = 0;
|
||||
mChronometer.setBase(mStartTime);
|
||||
mChronometer.start();
|
||||
if (!mCurrentLap.isRunning()) {
|
||||
mCurrentLap.resume();
|
||||
mUpdateHandler.asyncUpdate(mCurrentLap.getId(), mCurrentLap);
|
||||
}
|
||||
// This animator instance will end up having end() called on it. When
|
||||
// the table update prompts us to requery, onLoadFinished will be called as a result.
|
||||
// There, it calls startNewProgressAnimator() to end this animation and starts an
|
||||
@ -353,8 +316,6 @@ public class StopwatchFragment extends RecyclerViewFragment<
|
||||
mStartTime = 0;
|
||||
mPauseTime = 0;
|
||||
// ----------------------------------------------------------------------
|
||||
mCurrentLap = null;
|
||||
mPreviousLap = null;
|
||||
// No issues controlling the animator here, because onLoadFinished() can't
|
||||
// call through to startNewProgressBarAnimator(), because by that point
|
||||
// the chronometer won't be running.
|
||||
@ -376,8 +337,8 @@ public class StopwatchFragment extends RecyclerViewFragment<
|
||||
mActivityFab.get().setImageDrawable(running ? mPauseDrawable : mStartDrawable);
|
||||
}
|
||||
|
||||
private void startNewProgressBarAnimator() {
|
||||
final long timeRemaining = remainingTimeBetweenLaps();
|
||||
private void startNewProgressBarAnimator(Lap currentLap, Lap previousLap) {
|
||||
final long timeRemaining = remainingTimeBetweenLaps(currentLap, previousLap);
|
||||
if (timeRemaining <= 0) {
|
||||
mSeekBar.setVisibility(View.INVISIBLE);
|
||||
return;
|
||||
@ -388,8 +349,8 @@ public class StopwatchFragment extends RecyclerViewFragment<
|
||||
// This can't go in the onAnimationStart() callback because the listener is added
|
||||
// AFTER ProgressBarUtils.startNewAnimator() starts the animation.
|
||||
mSeekBar.setVisibility(View.VISIBLE);
|
||||
mProgressAnimator = ProgressBarUtils.startNewAnimator(
|
||||
mSeekBar, getCurrentLapProgressRatio(), timeRemaining);
|
||||
mProgressAnimator = ProgressBarUtils.startNewAnimator(mSeekBar,
|
||||
getCurrentLapProgressRatio(currentLap, previousLap), timeRemaining);
|
||||
mProgressAnimator.addListener(new Animator.AnimatorListener() {
|
||||
private boolean cancelled;
|
||||
|
||||
@ -424,26 +385,18 @@ public class StopwatchFragment extends RecyclerViewFragment<
|
||||
});
|
||||
}
|
||||
|
||||
private double getCurrentLapProgressRatio() {
|
||||
if (mPreviousLap == null)
|
||||
private double getCurrentLapProgressRatio(Lap currentLap, Lap previousLap) {
|
||||
if (previousLap == null)
|
||||
return 0;
|
||||
// The cast is necessary, or else we'd have integer division between two longs and we'd
|
||||
// always get zero since the numerator will always be less than the denominator.
|
||||
return remainingTimeBetweenLaps() / (double) mPreviousLap.elapsed();
|
||||
return remainingTimeBetweenLaps(currentLap, previousLap) / (double) previousLap.elapsed();
|
||||
}
|
||||
|
||||
private long remainingTimeBetweenLaps() {
|
||||
if (mCurrentLap == null || mPreviousLap == null)
|
||||
private long remainingTimeBetweenLaps(Lap currentLap, Lap previousLap) {
|
||||
if (currentLap == null || previousLap == null)
|
||||
return 0;
|
||||
return mPreviousLap.elapsed() - mCurrentLap.elapsed();
|
||||
}
|
||||
|
||||
// TODO: Delete.
|
||||
private void savePrefs() {
|
||||
mPrefs.edit().putLong(KEY_START_TIME, mStartTime)
|
||||
.putLong(KEY_PAUSE_TIME, mPauseTime)
|
||||
.putBoolean(KEY_CHRONOMETER_RUNNING, mChronometer.isRunning())
|
||||
.apply();
|
||||
return previousLap.elapsed() - currentLap.elapsed();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -7,22 +7,35 @@ import android.os.SystemClock;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.util.Log;
|
||||
|
||||
import com.philliphsu.clock2.ChronometerNotificationService;
|
||||
import com.philliphsu.clock2.MainActivity;
|
||||
import com.philliphsu.clock2.R;
|
||||
import com.philliphsu.clock2.timers.ChronometerDelegate;
|
||||
|
||||
public class StopwatchNotificationService extends ChronometerNotificationService {
|
||||
private static final String ACTION_ADD_LAP = "com.philliphsu.clock2.stopwatch.action.ADD_LAP";
|
||||
private static final String TAG = "StopwatchNotifService";
|
||||
|
||||
private AsyncLapsTableUpdateHandler mLapsTableUpdateHandler;
|
||||
public static final String ACTION_ADD_LAP = "com.philliphsu.clock2.stopwatch.action.ADD_LAP";
|
||||
public static final String ACTION_UPDATE_LAP_TITLE = "com.philliphsu.clock2.stopwatch.action.UPDATE_LAP_TITLE";
|
||||
|
||||
public static final String EXTRA_LAP_NUMBER = "com.philliphsu.clock2.stopwatch.extra.LAP_NUMBER";
|
||||
|
||||
private AsyncLapsTableUpdateHandler mUpdateHandler;
|
||||
private SharedPreferences mPrefs;
|
||||
private final ChronometerDelegate mDelegate = new ChronometerDelegate();
|
||||
private Lap mCurrentLap;
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
mLapsTableUpdateHandler = new AsyncLapsTableUpdateHandler(this, null);
|
||||
mUpdateHandler = new AsyncLapsTableUpdateHandler(this, null);
|
||||
mPrefs = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
// TODO: I'm afraid the base time here will be off by a considerable amount from the base time
|
||||
// set in StopwatchFragment.
|
||||
mDelegate.init();
|
||||
mDelegate.setShowCentiseconds(true, false);
|
||||
// TODO: I think we can make this a foreground service so even
|
||||
// if the process is killed, this service remains alive.
|
||||
}
|
||||
@ -58,27 +71,38 @@ public class StopwatchNotificationService extends ChronometerNotificationService
|
||||
|
||||
@Override
|
||||
protected void handleDefaultAction(Intent intent, int flags, long startId) {
|
||||
// TODO: Why do we need this check? Won't KEY_START_TIME always have a value of 0 here?
|
||||
if (mPrefs.getLong(StopwatchFragment.KEY_START_TIME, 0) == 0) {
|
||||
mCurrentLap = new Lap();
|
||||
mUpdateHandler.asyncInsert(mCurrentLap);
|
||||
}
|
||||
// TODO: String resource [Stopwatch: Lap %1$s]. If no laps, just [Stopwatch]
|
||||
setContentTitle(getString(R.string.stopwatch));
|
||||
syncNotificationWithStopwatchState(true/*always true*/);
|
||||
// We don't need to write anything to SharedPrefs because if we're here, StopwatchFragment
|
||||
// already wrote the necessary values to file.
|
||||
// will start this service again with ACTION_START_PAUSE, which will do the writing.
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleStartPauseAction(Intent intent, int flags, long startId) {
|
||||
// TODO: Tell StopwatchFragment to start/pause itself.. perhaps with an Intent?
|
||||
boolean running = mPrefs.getBoolean(StopwatchFragment.KEY_CHRONOMETER_RUNNING, false);
|
||||
SharedPreferences.Editor editor = mPrefs.edit();
|
||||
editor.putBoolean(StopwatchFragment.KEY_CHRONOMETER_RUNNING, !running);
|
||||
if (running) {
|
||||
editor.putLong(StopwatchFragment.KEY_PAUSE_TIME, SystemClock.elapsedRealtime());
|
||||
mCurrentLap.pause();
|
||||
mUpdateHandler.asyncUpdate(mCurrentLap.getId(), mCurrentLap);
|
||||
} else {
|
||||
long startTime = mPrefs.getLong(StopwatchFragment.KEY_START_TIME, 0);
|
||||
long pauseTime = mPrefs.getLong(StopwatchFragment.KEY_PAUSE_TIME, 0);
|
||||
startTime += SystemClock.elapsedRealtime() - pauseTime;
|
||||
editor.putLong(StopwatchFragment.KEY_START_TIME, startTime);
|
||||
editor.putLong(StopwatchFragment.KEY_PAUSE_TIME, 0);
|
||||
// TODO: Why do we need this check? Won't this lap always be paused here?
|
||||
if (!mCurrentLap.isRunning()) {
|
||||
mCurrentLap.resume();
|
||||
mUpdateHandler.asyncUpdate(mCurrentLap.getId(), mCurrentLap);
|
||||
}
|
||||
}
|
||||
editor.apply();
|
||||
syncNotificationWithStopwatchState(!running);
|
||||
@ -86,7 +110,6 @@ public class StopwatchNotificationService extends ChronometerNotificationService
|
||||
|
||||
@Override
|
||||
protected void handleStopAction(Intent intent, int flags, long startId) {
|
||||
// TODO: Tell StopwatchFragment to stop itself.. perhaps with an Intent?
|
||||
mPrefs.edit()
|
||||
.putLong(StopwatchFragment.KEY_START_TIME, 0)
|
||||
.putLong(StopwatchFragment.KEY_PAUSE_TIME, 0)
|
||||
@ -100,14 +123,33 @@ public class StopwatchNotificationService extends ChronometerNotificationService
|
||||
// We can either clear the laps table here, as we've done already, or do as the TODO above
|
||||
// says and tell StopwatchFragment to stop itself. The latter would also stop the
|
||||
// chronometer view if the fragment is still in view (i.e. app is still open).
|
||||
mLapsTableUpdateHandler.asyncClear();
|
||||
mCurrentLap = null;
|
||||
mUpdateHandler.asyncClear();
|
||||
stopSelf();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleAction(@NonNull String action, Intent intent, int flags, long startId) {
|
||||
if (ACTION_ADD_LAP.equals(action)) {
|
||||
mLapsTableUpdateHandler.asyncInsert(null/*TODO*/);
|
||||
if (mPrefs.getBoolean(StopwatchFragment.KEY_CHRONOMETER_RUNNING, false)) {
|
||||
String timestamp = mDelegate.formatElapsedTime(SystemClock.elapsedRealtime(),
|
||||
null/*Resources not needed here*/).toString();
|
||||
mCurrentLap.end(timestamp);
|
||||
mUpdateHandler.asyncUpdate(mCurrentLap.getId(), mCurrentLap);
|
||||
|
||||
Lap newLap = new Lap();
|
||||
mUpdateHandler.asyncInsert(newLap);
|
||||
mCurrentLap = newLap;
|
||||
}
|
||||
} else if (ACTION_UPDATE_LAP_TITLE.equals(action)) {
|
||||
int lapNumber = intent.getIntExtra(EXTRA_LAP_NUMBER, 0);
|
||||
if (lapNumber == 0) {
|
||||
Log.w(TAG, "Lap number was not passed in with intent");
|
||||
}
|
||||
// Unfortunately, the ID is only assigned when retrieving a Lap instance from
|
||||
// its cursor; the value here will always be 0.
|
||||
setContentTitle(getString(R.string.stopwatch_and_lap_number, lapNumber));
|
||||
updateNotification(true);
|
||||
} else {
|
||||
throw new IllegalArgumentException("StopwatchNotificationService cannot handle action " + action);
|
||||
}
|
||||
|
||||
@ -206,6 +206,8 @@
|
||||
|
||||
<string name="stopwatch">Stopwatch</string>
|
||||
<string name="lap">Lap</string>
|
||||
<!-- The notification title to use when there are laps in a stopwatch timing. An example is "Stopwatch - Lap 2". -->
|
||||
<string name="stopwatch_and_lap_number">Stopwatch \u002d Lap %1$d</string>
|
||||
|
||||
<!-- https://android.googlesource.com/platform/frameworks/base/+/master/core/res/res/values/strings.xml -->
|
||||
<!-- The representation of a time duration when negative. An example is -1:14. This can be used with a countdown timer for example.-->
|
||||
|
||||
Loading…
Reference in New Issue
Block a user