diff --git a/app/src/main/java/com/philliphsu/clock2/ChronometerNotificationService.java b/app/src/main/java/com/philliphsu/clock2/ChronometerNotificationService.java index 07a6cc6..ba82e63 100644 --- a/app/src/main/java/com/philliphsu/clock2/ChronometerNotificationService.java +++ b/app/src/main/java/com/philliphsu/clock2/ChronometerNotificationService.java @@ -236,6 +236,13 @@ public abstract class ChronometerNotificationService extends Service { mNoteBuilder.addAction(icon, actionTitle, pi); } + /** + * Cancels the notification with the pair ({@link #getNoteTag() tag}, id) + */ + protected final void cancelNotification(int id) { + mNotificationManager.cancel(getNoteTag(), id); + } + /** * Causes the handler thread's looper to terminate without processing * any more messages in the message queue. diff --git a/app/src/main/java/com/philliphsu/clock2/stopwatch/StopwatchController.java b/app/src/main/java/com/philliphsu/clock2/stopwatch/StopwatchController.java new file mode 100644 index 0000000..e1594d0 --- /dev/null +++ b/app/src/main/java/com/philliphsu/clock2/stopwatch/StopwatchController.java @@ -0,0 +1,10 @@ +package com.philliphsu.clock2.stopwatch; + +/** + * Created by Phillip Hsu on 9/11/2016. + */ +public final class StopwatchController { + + + +} diff --git a/app/src/main/java/com/philliphsu/clock2/stopwatch/StopwatchFragment.java b/app/src/main/java/com/philliphsu/clock2/stopwatch/StopwatchFragment.java index a9e966a..6468463 100644 --- a/app/src/main/java/com/philliphsu/clock2/stopwatch/StopwatchFragment.java +++ b/app/src/main/java/com/philliphsu/clock2/stopwatch/StopwatchFragment.java @@ -36,9 +36,11 @@ public class StopwatchFragment extends RecyclerViewFragment< LapCursor, LapsAdapter> { private static final String TAG = "StopwatchFragment"; - private static final String KEY_START_TIME = "start_time"; - private static final String KEY_PAUSE_TIME = "pause_time"; - private static final String KEY_CHRONOMETER_RUNNING = "chronometer_running"; + + // Exposed for StopwatchNotificationService + static final String KEY_START_TIME = "start_time"; + static final String KEY_PAUSE_TIME = "pause_time"; + static final String KEY_CHRONOMETER_RUNNING = "chronometer_running"; private long mStartTime; private long mPauseTime; diff --git a/app/src/main/java/com/philliphsu/clock2/stopwatch/StopwatchNotificationService.java b/app/src/main/java/com/philliphsu/clock2/stopwatch/StopwatchNotificationService.java index bfd5c13..fe470be 100644 --- a/app/src/main/java/com/philliphsu/clock2/stopwatch/StopwatchNotificationService.java +++ b/app/src/main/java/com/philliphsu/clock2/stopwatch/StopwatchNotificationService.java @@ -1,117 +1,109 @@ package com.philliphsu.clock2.stopwatch; -import android.app.NotificationManager; import android.app.PendingIntent; -import android.app.Service; -import android.content.Context; import android.content.Intent; -import android.os.IBinder; -import android.support.annotation.DrawableRes; -import android.support.v4.app.NotificationCompat; +import android.content.SharedPreferences; +import android.os.SystemClock; +import android.preference.PreferenceManager; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; -import com.philliphsu.clock2.ChronometerNotificationThread; +import com.philliphsu.clock2.ChronometerNotificationService; import com.philliphsu.clock2.MainActivity; import com.philliphsu.clock2.R; -public class StopwatchNotificationService extends Service { +public class StopwatchNotificationService extends ChronometerNotificationService { private static final String ACTION_ADD_LAP = "com.philliphsu.clock2.stopwatch.action.ADD_LAP"; - private static final String ACTION_START_PAUSE = "com.philliphsu.clock2.stopwatch.action.START_PAUSE"; - private static final String ACTION_STOP = "com.philliphsu.clock2.stopwatch.action.STOP"; - private NotificationCompat.Builder mNoteBuilder; - private NotificationManager mNotificationManager; private AsyncLapsTableUpdateHandler mLapsTableUpdateHandler; - private ChronometerNotificationThread mThread; + private SharedPreferences mPrefs; @Override public void onCreate() { super.onCreate(); - mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); mLapsTableUpdateHandler = new AsyncLapsTableUpdateHandler(this, null); - - // Create base note + mPrefs = PreferenceManager.getDefaultSharedPreferences(this); // TODO: I think we can make this a foreground service so even // if the process is killed, this service remains alive. - mNoteBuilder = new NotificationCompat.Builder(this) - .setSmallIcon(R.drawable.ic_stopwatch_24dp) - .setOngoing(true) -// .setUsesChronometer(true) // No way to pause/resume this native chronometer. - .setContentTitle(getString(R.string.stopwatch)); - Intent intent = new Intent(this, MainActivity.class); - intent.putExtra(null/*TODO:MainActivity.EXTRA_SHOW_PAGE*/, 2/*TODO:MainActivity.INDEX_STOPWATCH*/); - mNoteBuilder.setContentIntent(PendingIntent.getActivity(this, 0, intent, 0)); - - // TODO: Move adding these actions to the default case - // TODO: Change fillColor to white, to accommodate API < 21. - // Apparently, notifications on 21+ are automatically - // tinted to gray to contrast against the native notification background color. - addAction(ACTION_ADD_LAP, R.drawable.ic_add_lap_24dp, getString(R.string.lap)); - // TODO: Set icon and title according to state of stopwatch - addAction(ACTION_START_PAUSE, R.drawable.ic_pause_24dp, getString(R.string.pause)); - addAction(ACTION_STOP, R.drawable.ic_stop_24dp, getString(R.string.stop)); } @Override public void onDestroy() { super.onDestroy(); - quitThread(); + // After being cancelled due to time being up, sometimes the active timer notification posts again + // with a static 00:00 text, along with the Time's up notification. My theory is + // our thread has enough leeway to sneak in a final call to post the notification before it + // is actually quit(). + // As such, try cancelling the notification with this (tag, id) pair again. + cancelNotification(0); } @Override - public int onStartCommand(Intent intent, int flags, int startId) { - if (intent != null) { - final String action = intent.getAction(); - if (action == null) { - // TODO: Read the stopwatch's start time in shared prefs. - mNoteBuilder.setWhen(System.currentTimeMillis()); - // TODO: Lap # content text - mNoteBuilder.setContentText("Lap 1"); - // Use class name as tag instead of defining our own tag constant, because - // the latter is limited to 23 (?) chars if you also want to use it as - // a log tag. - mNotificationManager.notify(getClass().getName(), 0, mNoteBuilder.build()); - } else { - switch (action) { - case ACTION_ADD_LAP: -// mLapsTableUpdateHandler.asyncInsert(null/*TODO*/); - break; - case ACTION_START_PAUSE: - break; - case ACTION_STOP: - // Cancels all of the notifications issued by *this instance* of the manager, - // not those of any other instances (in this app or otherwise). - // TODO: We could cancel by (tag, id) if we cared. - mNotificationManager.cancelAll(); - break; - } - } + protected int getSmallIcon() { + return R.drawable.ic_stopwatch_24dp; + } + + @Nullable + @Override + protected PendingIntent getContentIntent() { + Intent intent = new Intent(this, MainActivity.class); + intent.putExtra(null/*TODO:MainActivity.EXTRA_SHOW_PAGE*/, 2/*TODO:MainActivity.INDEX_STOPWATCH*/); + return PendingIntent.getActivity(this, 0, intent, 0); + } + + @Override + protected boolean isCountDown() { + return false; + } + + @Override + protected void handleDefaultAction(Intent intent, int flags, long startId) { + // 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. + } + + @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); + syncNotificationWithStopwatchState(!running); + SharedPreferences.Editor editor = mPrefs.edit(); + editor.putBoolean(StopwatchFragment.KEY_CHRONOMETER_RUNNING, !running); + } + + @Override + protected void handleStopAction(Intent intent, int flags, long startId) { + // TODO: Tell StopwatchFragment to stop itself.. perhaps with an Intent? + stopSelf(); + } + + @Override + protected void handleAction(@NonNull String action, Intent intent, int flags, long startId) { + if (ACTION_ADD_LAP.equals(action)) { + mLapsTableUpdateHandler.asyncInsert(null/*TODO*/); + } else { + throw new IllegalArgumentException("StopwatchNotificationService cannot handle action " + action); } - return super.onStartCommand(intent, flags, startId); } - @Override - public IBinder onBind(Intent intent) { - return null; - } + private void syncNotificationWithStopwatchState(boolean running) { + clearActions(); + // TODO: Change fillColor to white, to accommodate API < 21. + // Apparently, notifications on 21+ are automatically + // tinted to gray to contrast against the native notification background color. + // + // No request code needed, so use 0. + addAction(ACTION_ADD_LAP, R.drawable.ic_add_lap_24dp, getString(R.string.lap), 0); + addStartPauseAction(running, 0); + addStopAction(0); - /** - * Builds and adds the specified action to the notification's mNoteBuilder. - */ - private void addAction(String action, @DrawableRes int icon, String actionTitle) { - Intent intent = new Intent(this, StopwatchNotificationService.class) - .setAction(action); - PendingIntent pi = PendingIntent.getService(this, 0/*no requestCode*/, - intent, 0/*no flags*/); - mNoteBuilder.addAction(icon, actionTitle, pi); - } - - /** - * Causes the handler thread's looper to terminate without processing - * any more messages in the message queue. - */ - private void quitThread() { - if (mThread != null && mThread.isAlive()) { - mThread.quit(); + quitCurrentThread(); + if (running) { + // TODO: Read the stopwatch's start time in shared prefs. + startNewThread(0, SystemClock.elapsedRealtime()); } } } diff --git a/app/src/main/java/com/philliphsu/clock2/timers/TimerNotificationService.java b/app/src/main/java/com/philliphsu/clock2/timers/TimerNotificationService.java index e30b295..df0fe67 100644 --- a/app/src/main/java/com/philliphsu/clock2/timers/TimerNotificationService.java +++ b/app/src/main/java/com/philliphsu/clock2/timers/TimerNotificationService.java @@ -1,6 +1,5 @@ package com.philliphsu.clock2.timers; -import android.app.NotificationManager; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; @@ -97,8 +96,7 @@ public class TimerNotificationService extends ChronometerNotificationService { // our thread has enough leeway to sneak in a final call to post the notification before it // is actually quit(). // As such, try cancelling the notification with this (tag, id) pair again. - NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); - nm.cancel(getNoteTag(), mTimer.getIntId()); + cancelNotification(mTimer.getIntId()); } @Override