diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index ab5cf5f..8a02120 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -21,13 +21,13 @@
-
-
+ -->
-
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/com/philliphsu/clock2/Alarm.java b/app/src/main/java/com/philliphsu/clock2/Alarm.java
index c4c430a..c1f2f09 100644
--- a/app/src/main/java/com/philliphsu/clock2/Alarm.java
+++ b/app/src/main/java/com/philliphsu/clock2/Alarm.java
@@ -203,8 +203,9 @@ public abstract class Alarm extends ObjectWithId implements JsonSerializable, Pa
return !ignoreUpcomingRingTime && ringsIn() <= TimeUnit.HOURS.toMillis(hours);
}
+ // TODO: Rename to getIntId() so usages refer to ObjectWithId#getIntId(), then delete this method.
public int intId() {
- return (int) getId();
+ return getIntId();
}
// TODO: Remove method signature from JsonSerializable interface.
diff --git a/app/src/main/java/com/philliphsu/clock2/AsyncTimersTableUpdateHandler.java b/app/src/main/java/com/philliphsu/clock2/AsyncTimersTableUpdateHandler.java
index 295b35c..2e598de 100644
--- a/app/src/main/java/com/philliphsu/clock2/AsyncTimersTableUpdateHandler.java
+++ b/app/src/main/java/com/philliphsu/clock2/AsyncTimersTableUpdateHandler.java
@@ -1,14 +1,22 @@
package com.philliphsu.clock2;
+import android.app.AlarmManager;
+import android.app.PendingIntent;
import android.content.Context;
+import android.content.Intent;
+import android.os.SystemClock;
+import android.util.Log;
import com.philliphsu.clock2.alarms.ScrollHandler;
import com.philliphsu.clock2.model.TimersTableManager;
+import com.philliphsu.clock2.timers.TimerNotificationService;
+import com.philliphsu.clock2.timers.TimesUpActivity;
/**
* Created by Phillip Hsu on 8/2/2016.
*/
public final class AsyncTimersTableUpdateHandler extends AsyncDatabaseTableUpdateHandler {
+ private static final String TAG = "TimersTableUpdater"; // TAG max 23 chars
public AsyncTimersTableUpdateHandler(Context context, ScrollHandler scrollHandler) {
super(context, scrollHandler);
@@ -21,16 +29,55 @@ public final class AsyncTimersTableUpdateHandler extends AsyncDatabaseTableUpdat
@Override
protected void onPostAsyncDelete(Integer result, Timer timer) {
- // TODO: Cancel the alarm scheduled for this timer
+ cancelAlarm(timer);
}
@Override
protected void onPostAsyncInsert(Long result, Timer timer) {
- // TODO: if running, schedule alarm
+ Log.d(TAG, "onPostAsyncInsert()");
+ if (timer.isRunning()) {
+ Log.d(TAG, "Scheduling alarm for timer launch");
+ scheduleAlarm(timer);
+ }
}
@Override
protected void onPostAsyncUpdate(Long result, Timer timer) {
- // TODO: cancel and reschedule
+ if (timer.isRunning()) {
+ // We don't need to cancel the previous alarm, because this one
+ // will remove and replace it.
+ scheduleAlarm(timer);
+ } else {
+ cancelAlarm(timer);
+ }
+ }
+
+ // TODO: Consider changing to just a long id param
+ private PendingIntent createTimesUpIntent(Timer timer) {
+ Intent intent = new Intent(getContext(), TimesUpActivity.class);
+// intent.putExtra(TimesUpActivity.EXTRA_ITEM_ID, timer.getId());
+ // There's no point to determining whether to retrieve a previous instance, because
+ // we chose to ignore it since we had issues with NPEs. TODO: Perhaps these issues
+ // were caused by you using the same reference variable for every Intent/PI that
+ // needed to be recreated, and you reassigning the reference each time you were done with
+ // one of them, which leaves the one before unreferenced and hence eligible for GC.
+ return PendingIntent.getActivity(getContext(), timer.getIntId(), intent, PendingIntent.FLAG_CANCEL_CURRENT);
+ }
+
+ private void scheduleAlarm(Timer timer) {
+ Log.d(TAG, String.format("now = %d, endTime = %d", SystemClock.elapsedRealtime(), timer.endTime()));
+ AlarmManager am = (AlarmManager) getContext().getSystemService(Context.ALARM_SERVICE);
+ am.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, timer.endTime(), createTimesUpIntent(timer));
+ TimerNotificationService.showNotification(getContext(), timer.getId());
+ }
+
+ private void cancelAlarm(Timer timer) {
+ // Cancel the alarm scheduled. If one was never scheduled, does nothing.
+ AlarmManager am = (AlarmManager) getContext().getSystemService(Context.ALARM_SERVICE);
+ PendingIntent pi = createTimesUpIntent(timer);
+ // Now can't be null
+ am.cancel(pi);
+ pi.cancel();
+ TimerNotificationService.cancelNotification(getContext(), timer.getId());
}
}
diff --git a/app/src/main/java/com/philliphsu/clock2/alarms/AlarmActivity.java b/app/src/main/java/com/philliphsu/clock2/alarms/AlarmActivity.java
new file mode 100644
index 0000000..39ce01f
--- /dev/null
+++ b/app/src/main/java/com/philliphsu/clock2/alarms/AlarmActivity.java
@@ -0,0 +1,78 @@
+package com.philliphsu.clock2.alarms;
+
+import android.os.Bundle;
+import android.support.v4.content.Loader;
+import android.view.View;
+import android.widget.Button;
+
+import com.philliphsu.clock2.Alarm;
+import com.philliphsu.clock2.R;
+import com.philliphsu.clock2.model.AlarmLoader;
+import com.philliphsu.clock2.ringtone.RingtoneActivity;
+import com.philliphsu.clock2.util.AlarmController;
+
+public class AlarmActivity extends RingtoneActivity {
+
+ private AlarmController mAlarmController;
+ // TODO: Write a getter method instead in the base class?
+ private Alarm mAlarm;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mAlarmController = new AlarmController(this, null);
+ // TODO: Butterknife binding
+ Button snooze = (Button) findViewById(R.id.btn_snooze);
+ snooze.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ snooze();
+ }
+ });
+ Button dismiss = (Button) findViewById(R.id.btn_dismiss);
+ dismiss.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ dismiss();
+ }
+ });
+ }
+
+ @Override
+ public Loader onCreateLoader(long id) {
+ return new AlarmLoader(this, id);
+ }
+
+ @Override
+ public void onLoadFinished(Loader loader, Alarm data) {
+ super.onLoadFinished(loader, data);
+ mAlarm = data;
+ if (data != null) {
+ // TODO: If the upcoming alarm notification isn't present, verify other notifications aren't affected.
+ // This could be the case if we're starting a new instance of this activity after leaving the first launch.
+ mAlarmController.removeUpcomingAlarmNotification(data);
+ }
+ }
+
+ @Override
+ public int layoutResource() {
+ return R.layout.activity_ringtone;
+ }
+
+ private void snooze() {
+ if (mAlarm != null) {
+ mAlarmController.snoozeAlarm(mAlarm);
+ }
+ // Can't call dismiss() because we don't want to also call cancelAlarm()! Why? For example,
+ // we don't want the alarm, if it has no recurrence, to be turned off right now.
+ stopAndFinish();
+ }
+
+ private void dismiss() {
+ if (mAlarm != null) {
+ // TODO do we really need to cancel the intent and alarm?
+ mAlarmController.cancelAlarm(mAlarm, false);
+ }
+ stopAndFinish();
+ }
+}
diff --git a/app/src/main/java/com/philliphsu/clock2/editalarm/EditAlarmActivity.java b/app/src/main/java/com/philliphsu/clock2/editalarm/EditAlarmActivity.java
index 1049d56..53b80b3 100644
--- a/app/src/main/java/com/philliphsu/clock2/editalarm/EditAlarmActivity.java
+++ b/app/src/main/java/com/philliphsu/clock2/editalarm/EditAlarmActivity.java
@@ -28,8 +28,8 @@ import com.philliphsu.clock2.BaseActivity;
import com.philliphsu.clock2.DaysOfWeek;
import com.philliphsu.clock2.R;
import com.philliphsu.clock2.SharedPreferencesHelper;
+import com.philliphsu.clock2.alarms.AlarmActivity;
import com.philliphsu.clock2.model.AlarmLoader;
-import com.philliphsu.clock2.ringtone.RingtoneActivity;
import com.philliphsu.clock2.util.AlarmController;
import com.philliphsu.clock2.util.AlarmUtils;
import com.philliphsu.clock2.util.DateFormatUtils;
@@ -543,8 +543,8 @@ public class EditAlarmActivity extends BaseActivity implements
@Override
public void cancelAlarm(Alarm alarm, boolean showToast) {
new AlarmController(this, mMainContent).cancelAlarm(alarm, true);
- if (RingtoneActivity.isAlive()) {
- LocalBroadcastHelper.sendBroadcast(this, RingtoneActivity.ACTION_FINISH);
+ if (AlarmActivity.isAlive()) {
+ LocalBroadcastHelper.sendBroadcast(this, AlarmActivity.ACTION_FINISH);
}
}
diff --git a/app/src/main/java/com/philliphsu/clock2/model/AlarmLoader.java b/app/src/main/java/com/philliphsu/clock2/model/AlarmLoader.java
index 0066ae8..4c22470 100644
--- a/app/src/main/java/com/philliphsu/clock2/model/AlarmLoader.java
+++ b/app/src/main/java/com/philliphsu/clock2/model/AlarmLoader.java
@@ -11,6 +11,8 @@ public class AlarmLoader extends DataLoader {
private long mAlarmId;
+ // TODO: Consider writing a super ctor that has the id param, so
+ // subclasses don't need to write their own.
public AlarmLoader(Context context, long alarmId) {
super(context);
mAlarmId = alarmId;
diff --git a/app/src/main/java/com/philliphsu/clock2/model/DataLoader.java b/app/src/main/java/com/philliphsu/clock2/model/DataLoader.java
index baaea25..bd33bb4 100644
--- a/app/src/main/java/com/philliphsu/clock2/model/DataLoader.java
+++ b/app/src/main/java/com/philliphsu/clock2/model/DataLoader.java
@@ -6,6 +6,9 @@ import android.support.v4.content.AsyncTaskLoader;
/**
* Created by Phillip Hsu on 6/30/2016.
*/
+// TODO: Consider adding a DatabaseTableManager type param, so we can then
+// implement loadInBackground for subclasses. You would, however, need to write
+// an abstract method getTableManager() that subclasses implement for us.
public abstract class DataLoader extends AsyncTaskLoader {
private D mData;
diff --git a/app/src/main/java/com/philliphsu/clock2/model/ObjectWithId.java b/app/src/main/java/com/philliphsu/clock2/model/ObjectWithId.java
index 1bfbc54..09c9020 100644
--- a/app/src/main/java/com/philliphsu/clock2/model/ObjectWithId.java
+++ b/app/src/main/java/com/philliphsu/clock2/model/ObjectWithId.java
@@ -15,4 +15,8 @@ public abstract class ObjectWithId {
public final void setId(long id) {
this.id = id;
}
+
+ public final int getIntId() {
+ return (int) id;
+ }
}
diff --git a/app/src/main/java/com/philliphsu/clock2/model/TimerLoader.java b/app/src/main/java/com/philliphsu/clock2/model/TimerLoader.java
new file mode 100644
index 0000000..ba598ac
--- /dev/null
+++ b/app/src/main/java/com/philliphsu/clock2/model/TimerLoader.java
@@ -0,0 +1,25 @@
+package com.philliphsu.clock2.model;
+
+import android.content.Context;
+
+import com.philliphsu.clock2.Timer;
+
+/**
+ * Created by Phillip Hsu on 8/3/2016.
+ */
+public class TimerLoader extends DataLoader {
+
+ private long mTimerId;
+
+ // TODO: Consider writing a super ctor that has the id param, so
+ // subclasses don't need to write their own.
+ public TimerLoader(Context context, long timerId) {
+ super(context);
+ mTimerId = timerId;
+ }
+
+ @Override
+ public Timer loadInBackground() {
+ return new TimersTableManager(getContext()).queryItem(mTimerId).getItem();
+ }
+}
diff --git a/app/src/main/java/com/philliphsu/clock2/ringtone/RingtoneActivity.java b/app/src/main/java/com/philliphsu/clock2/ringtone/RingtoneActivity.java
index 96acecb..d32d4ca 100644
--- a/app/src/main/java/com/philliphsu/clock2/ringtone/RingtoneActivity.java
+++ b/app/src/main/java/com/philliphsu/clock2/ringtone/RingtoneActivity.java
@@ -4,26 +4,20 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
+import android.support.annotation.LayoutRes;
+import android.support.v4.app.LoaderManager.LoaderCallbacks;
import android.support.v4.content.Loader;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.view.WindowManager;
-import android.widget.Button;
-import com.philliphsu.clock2.Alarm;
-import com.philliphsu.clock2.R;
-import com.philliphsu.clock2.model.AlarmLoader;
-import com.philliphsu.clock2.util.AlarmController;
import com.philliphsu.clock2.util.LocalBroadcastHelper;
/**
* An example full-screen activity that shows and hides the system UI (i.e.
* status bar and navigation/system bar) with user interaction.
- *
- * TODO: Make this abstract and make appropriate subclasses for Alarms and Timers.
*/
-public class RingtoneActivity extends AppCompatActivity implements
- android.support.v4.app.LoaderManager.LoaderCallbacks {
+public abstract class RingtoneActivity extends AppCompatActivity implements LoaderCallbacks {
private static final String TAG = "RingtoneActivity";
// Shared with RingtoneService
@@ -32,14 +26,19 @@ public class RingtoneActivity extends AppCompatActivity implements
private static boolean sIsAlive = false;
- private long mAlarmId;
- private Alarm mAlarm;
- private AlarmController mAlarmController;
+ private long mItemId;
+ private T mItem;
+
+ public abstract Loader onCreateLoader(long itemId);
+
+ // TODO: Should we extend from BaseActivity instead?
+ @LayoutRes
+ public abstract int layoutResource();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_ringtone);
+ setContentView(layoutResource());
sIsAlive = true;
getWindow().addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);
@@ -47,8 +46,8 @@ public class RingtoneActivity extends AppCompatActivity implements
getWindow().addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
- mAlarmId = getIntent().getLongExtra(EXTRA_ITEM_ID, -1);
- if (mAlarmId < 0) {
+ mItemId = getIntent().getLongExtra(EXTRA_ITEM_ID, -1);
+ if (mItemId < 0) {
throw new IllegalStateException("Cannot start RingtoneActivity without item's id");
}
// The reason we don't use a thread to load the alarm is because this is an
@@ -57,43 +56,31 @@ public class RingtoneActivity extends AppCompatActivity implements
getSupportLoaderManager().initLoader(0, null, this);
Intent intent = new Intent(this, RingtoneService.class)
- .putExtra(EXTRA_ITEM_ID, mAlarmId);
+ .putExtra(EXTRA_ITEM_ID, mItemId);
startService(intent);
-
- mAlarmController = new AlarmController(this, null);
-
- // TODO: Butterknife binding
- Button snooze = (Button) findViewById(R.id.btn_snooze);
- snooze.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- snooze();
- }
- });
- Button dismiss = (Button) findViewById(R.id.btn_dismiss);
- dismiss.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- dismiss();
- }
- });
}
@Override
protected void onResume() {
super.onResume();
+ // TODO: Do we need this anymore? I think this broadcast was only sent from
+ // EditAlarmActivity?
LocalBroadcastHelper.registerReceiver(this, mFinishReceiver, ACTION_FINISH);
}
@Override
protected void onPause() {
super.onPause();
+ // TODO: Do we need this anymore? I think this broadcast was only sent from
+ // EditAlarmActivity?
LocalBroadcastHelper.unregisterReceiver(this, mFinishReceiver);
}
@Override
protected void onNewIntent(Intent intent) {
//super.onNewIntent(intent); // Not needed since no fragments hosted?
+ // TODO: Do we need this anymore? I think the broadcast that calls through to
+ // this was only sent from EditAlarmActivity?
// Notifies alarm missed and stops the service
LocalBroadcastHelper.sendBroadcast(this, RingtoneService.ACTION_NOTIFY_MISSED);
@@ -138,22 +125,17 @@ public class RingtoneActivity extends AppCompatActivity implements
}
@Override
- public Loader onCreateLoader(int id, Bundle args) {
- return new AlarmLoader(this, mAlarmId);
+ public Loader onCreateLoader(int id, Bundle args) {
+ return onCreateLoader(mItemId);
}
@Override
- public void onLoadFinished(Loader loader, Alarm data) {
- mAlarm = data;
- if (mAlarm != null) {
- // TODO: If the upcoming alarm notification isn't present, verify other notifications aren't affected.
- // This could be the case if we're starting a new instance of this activity after leaving the first launch.
- mAlarmController.removeUpcomingAlarmNotification(mAlarm);
- }
+ public void onLoadFinished(Loader loader, T data) {
+ mItem = data;
}
@Override
- public void onLoaderReset(Loader loader) {
+ public void onLoaderReset(Loader loader) {
// Do nothing
}
@@ -161,28 +143,17 @@ public class RingtoneActivity extends AppCompatActivity implements
return sIsAlive;
}
- private void snooze() {
- if (mAlarm != null) {
- mAlarmController.snoozeAlarm(mAlarm);
- }
- // Can't call dismiss() because we don't want to also call cancelAlarm()! Why? For example,
- // we don't want the alarm, if it has no recurrence, to be turned off right now.
- stopAndFinish();
- }
-
- private void dismiss() {
- if (mAlarm != null) {
- // TODO do we really need to cancel the intent and alarm?
- mAlarmController.cancelAlarm(mAlarm, false);
- }
- stopAndFinish();
- }
-
- private void stopAndFinish() {
+ /**
+ * Exposed to subclasses so they can force us to stop the
+ * ringtone and finish us.
+ */
+ protected final void stopAndFinish() {
stopService(new Intent(this, RingtoneService.class));
finish();
}
+ // TODO: Do we need this anymore? I think this broadcast was only sent from
+ // EditAlarmActivity?
private final BroadcastReceiver mFinishReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
diff --git a/app/src/main/java/com/philliphsu/clock2/ringtone/RingtoneService.java b/app/src/main/java/com/philliphsu/clock2/ringtone/RingtoneService.java
index a236628..8f85939 100644
--- a/app/src/main/java/com/philliphsu/clock2/ringtone/RingtoneService.java
+++ b/app/src/main/java/com/philliphsu/clock2/ringtone/RingtoneService.java
@@ -220,6 +220,10 @@ public class RingtoneService extends Service { // TODO: abstract this, make subc
}
private void finishActivity() {
+ // I think this will be received by all instances of RingtoneActivity
+ // subclasses in memory.. but since we realistically expect only one
+ // instance alive at any given time, we don't need to worry about having
+ // to restrict the broadcast to only the subclass that's alive.
LocalBroadcastHelper.sendBroadcast(this, RingtoneActivity.ACTION_FINISH);
}
diff --git a/app/src/main/java/com/philliphsu/clock2/timers/TimerController.java b/app/src/main/java/com/philliphsu/clock2/timers/TimerController.java
index 6f5bfeb..3364cd4 100644
--- a/app/src/main/java/com/philliphsu/clock2/timers/TimerController.java
+++ b/app/src/main/java/com/philliphsu/clock2/timers/TimerController.java
@@ -1,7 +1,5 @@
package com.philliphsu.clock2.timers;
-import android.widget.ImageButton;
-
import com.philliphsu.clock2.Timer;
/**
@@ -9,27 +7,26 @@ import com.philliphsu.clock2.Timer;
*/
public class TimerController {
private final Timer mTimer;
- private final CountdownChronometer mChronometer;
- private final ImageButton mAddOneMinute;
- private final ImageButton mStartPause;
- private final ImageButton mStop;
- public TimerController(Timer timer, CountdownChronometer chronometer, ImageButton addOneMinute,
- ImageButton startPause, ImageButton stop) {
- mTimer = timer;
- mChronometer = chronometer;
- mAddOneMinute = addOneMinute;
- mStartPause = startPause;
- mStop = stop;
-
-// init();
+ /**
+ * Calls the appropriate state on the given Timer, based on
+ * its current state.
+ */
+ public static void startPause(Timer timer) {
+ if (timer.hasStarted()) {
+ if (timer.isRunning()) {
+ timer.pause();
+ } else {
+ timer.resume();
+ }
+ } else {
+ timer.start();
+ }
+ }
+
+ public TimerController(Timer timer) {
+ mTimer = timer;
}
-
-// private void init() {
-// mChronometer.setBase(SystemClock.elapsedRealtime() + mTimer.duration());
-// updateStartPauseIcon();
-// setSecondaryButtonsVisible(false);
-// }
public void start() {
mTimer.start();
diff --git a/app/src/main/java/com/philliphsu/clock2/timers/TimerNotificationService.java b/app/src/main/java/com/philliphsu/clock2/timers/TimerNotificationService.java
new file mode 100644
index 0000000..4318cc1
--- /dev/null
+++ b/app/src/main/java/com/philliphsu/clock2/timers/TimerNotificationService.java
@@ -0,0 +1,171 @@
+package com.philliphsu.clock2.timers;
+
+import android.app.IntentService;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.support.annotation.DrawableRes;
+import android.support.v4.app.NotificationCompat;
+
+import com.philliphsu.clock2.MainActivity;
+import com.philliphsu.clock2.R;
+import com.philliphsu.clock2.Timer;
+import com.philliphsu.clock2.model.TimersTableManager;
+
+/**
+ * An {@link IntentService} subclass for handling asynchronous task requests in
+ * a service on a separate handler thread.
+ *
+ * TODO: Customize class - update intent actions, extra parameters and static
+ * helper methods.
+ */
+public class TimerNotificationService extends IntentService {
+ private static final String TAG = "TimerNotificationService";
+
+ public static final String ACTION_ADD_ONE_MINUTE = "com.philliphsu.clock2.timers.action.ADD_ONE_MINUTE";
+ public static final String ACTION_START_PAUSE = "com.philliphsu.clock2.timers.action.START_PAUSE";
+ public static final String ACTION_STOP = "com.philliphsu.clock2.timers.action.STOP";
+
+ public static final String EXTRA_TIMER_ID = "com.philliphsu.clock2.timers.extra.TIMER_ID";
+
+ private TimersTableManager mTableManager;
+
+ public TimerNotificationService() {
+ super("TimerNotificationService");
+ }
+
+ /**
+ * Helper method to start this Service for its default action: to show
+ * the notification for the Timer with the given id.
+ */
+ public static void showNotification(Context context, long timerId) {
+ Intent intent = new Intent(context, TimerNotificationService.class);
+ intent.putExtra(EXTRA_TIMER_ID, timerId);
+ context.startService(intent);
+ }
+
+ /**
+ * Helper method to cancel the notification previously shown from calling
+ * {@link #showNotification(Context, long)}. This does NOT start the Service
+ * and call through to {@link #onHandleIntent(Intent)}.
+ * @param timerId the id of the Timer associated with the notification
+ * you want to cancel
+ */
+ public static void cancelNotification(Context context, long timerId) {
+ NotificationManager nm = (NotificationManager)
+ context.getSystemService(Context.NOTIFICATION_SERVICE);
+ nm.cancel(TAG, (int) timerId);
+ }
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ mTableManager = new TimersTableManager(this);
+ }
+
+ @Override
+ protected void onHandleIntent(Intent intent) {
+ if (intent != null) {
+ final long timerId = intent.getLongExtra(EXTRA_TIMER_ID, -1);
+ if (timerId == -1) {
+ throw new IllegalStateException("Did not pass in timer id");
+ }
+ final String action = intent.getAction();
+ if (action == null) {
+ showNotification(timerId);
+ } else if (ACTION_ADD_ONE_MINUTE.equals(action)) {
+ handleAddOneMinute(timerId);
+ } else if (ACTION_START_PAUSE.equals(action)) {
+ handleStartPause(timerId);
+ } else if (ACTION_STOP.equals(action)) {
+ handleStop(timerId);
+ }
+ }
+ }
+
+ private void showNotification(long timerId) {
+ Timer timer = getTimer(timerId);
+
+ // Base note
+ NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
+ // TODO: correct icon
+ .setSmallIcon(R.drawable.ic_half_day_1_black_24dp)
+ .setShowWhen(false)
+ .setOngoing(true);
+ // TODO: Set content intent so that when clicked, we launch
+ // TimersFragment and scroll to the given timer id. The following
+ // is merely pseudocode.
+ Intent contentIntent = new Intent(this, MainActivity.class);
+ contentIntent.putExtra(null/*TODO:MainActivity.EXTRA_SHOW_PAGE*/,
+ 1/*TODO:The tab index of the timers page*/);
+ contentIntent.putExtra(null/*TODO:MainActivity.EXTRA_SCROLL_TO_ID*/,
+ timerId);
+ builder.setContentIntent(PendingIntent.getActivity(
+ this,
+ 0, // TODO: Request code not needed? Since any multiple notifications
+ // should be able to use the same PendingIntent for this action....
+ // unless the underlying *Intent* and its id extra are overwritten
+ // per notification when retrieving the PendingIntent..
+ contentIntent,
+ 0/*Shouldn't need a flag..*/));
+ // TODO: Use a handler to continually update the countdown text
+
+ String title = timer.label();
+ if (title.isEmpty()) {
+ title = getString(R.string.timer);
+ }
+ builder.setContentTitle(title);
+
+ addAction(builder, ACTION_ADD_ONE_MINUTE,
+ timer.getId(), R.drawable.ic_add_circle_24dp/*TODO: correct icon*/);
+ addAction(builder, ACTION_START_PAUSE,
+ timer.getId(), R.drawable.ic_add_circle_24dp/*TODO: correct icon*/);
+ addAction(builder, ACTION_STOP,
+ timer.getId(), R.drawable.ic_add_circle_24dp/*TODO: correct icon*/);
+
+ NotificationManager nm = (NotificationManager)
+ getSystemService(Context.NOTIFICATION_SERVICE);
+ nm.notify(TAG, timer.getIntId(), builder.build());
+ }
+
+ /**
+ * Builds and adds the specified action to the notification's builder.
+ */
+ private void addAction(NotificationCompat.Builder noteBuilder, String action,
+ long timerId, @DrawableRes int icon) {
+ Intent intent = new Intent(this, TimerNotificationService.class)
+ .setAction(action)
+ .putExtra(EXTRA_TIMER_ID, timerId);
+ PendingIntent pi = PendingIntent.getService(this,
+ (int) timerId, intent, 0/*no flags*/);
+ noteBuilder.addAction(icon, ""/*no action title*/, pi);
+ }
+
+ private void handleAddOneMinute(long timerId) {
+ Timer timer = getTimer(timerId);
+ timer.addOneMinute();
+ updateTimer(timer);
+ // TODO: Verify the notification countdown is extended by one minute.
+ }
+
+ private void handleStartPause(long timerId) {
+ Timer t = getTimer(timerId);
+ TimerController.startPause(t);
+ updateTimer(t);
+ }
+
+ private void handleStop(long timerId) {
+ Timer t = getTimer(timerId);
+ t.stop();
+ updateTimer(t);
+ }
+
+ private void updateTimer(Timer timer) {
+ mTableManager.updateItem(timer.getId(), timer);
+ }
+
+ private Timer getTimer(long timerId) {
+ return mTableManager.queryItem(timerId).getItem();
+ }
+}
diff --git a/app/src/main/java/com/philliphsu/clock2/timers/TimerViewHolder.java b/app/src/main/java/com/philliphsu/clock2/timers/TimerViewHolder.java
index 4948193..902d660 100644
--- a/app/src/main/java/com/philliphsu/clock2/timers/TimerViewHolder.java
+++ b/app/src/main/java/com/philliphsu/clock2/timers/TimerViewHolder.java
@@ -1,6 +1,5 @@
package com.philliphsu.clock2.timers;
-import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageButton;
@@ -52,17 +51,7 @@ public class TimerViewHolder extends BaseViewHolder {
@OnClick(R.id.start_pause)
void startPause() {
- Timer t = getItem();
- if (t.isRunning()) {
-// mController.pause();
- t.pause();
- } else {
- if (t.hasStarted()) {
- t.resume();
- } else {
- t.start();
- }
- }
+ TimerController.startPause(getItem());
// Persist value changes
update();
}
diff --git a/app/src/main/java/com/philliphsu/clock2/timers/TimesUpActivity.java b/app/src/main/java/com/philliphsu/clock2/timers/TimesUpActivity.java
new file mode 100644
index 0000000..6549cfd
--- /dev/null
+++ b/app/src/main/java/com/philliphsu/clock2/timers/TimesUpActivity.java
@@ -0,0 +1,22 @@
+package com.philliphsu.clock2.timers;
+
+import android.os.Bundle;
+import android.support.v7.app.AppCompatActivity;
+
+public class TimesUpActivity extends AppCompatActivity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ }
+
+// @Override
+// public Loader onCreateLoader(long itemId) {
+// return new TimerLoader(this, itemId);
+// }
+
+// @Override
+// public int layoutResource() {
+// return R.layout.activity_ringtone;
+// }
+}
diff --git a/app/src/main/java/com/philliphsu/clock2/util/AlarmController.java b/app/src/main/java/com/philliphsu/clock2/util/AlarmController.java
index 468ec1c..9cba57e 100644
--- a/app/src/main/java/com/philliphsu/clock2/util/AlarmController.java
+++ b/app/src/main/java/com/philliphsu/clock2/util/AlarmController.java
@@ -12,8 +12,8 @@ import com.philliphsu.clock2.Alarm;
import com.philliphsu.clock2.PendingAlarmScheduler;
import com.philliphsu.clock2.R;
import com.philliphsu.clock2.UpcomingAlarmReceiver;
+import com.philliphsu.clock2.alarms.AlarmActivity;
import com.philliphsu.clock2.model.AlarmsTableManager;
-import com.philliphsu.clock2.ringtone.RingtoneActivity;
import com.philliphsu.clock2.ringtone.RingtoneService;
import static android.app.PendingIntent.FLAG_CANCEL_CURRENT;
@@ -183,8 +183,8 @@ public final class AlarmController {
private PendingIntent alarmIntent(Alarm alarm, boolean retrievePrevious) {
// TODO: Use appropriate subclass instead
- Intent intent = new Intent(mAppContext, RingtoneActivity.class)
- .putExtra(RingtoneActivity.EXTRA_ITEM_ID, alarm.id());
+ Intent intent = new Intent(mAppContext, AlarmActivity.class)
+ .putExtra(AlarmActivity.EXTRA_ITEM_ID, alarm.id());
int flag = retrievePrevious ? FLAG_NO_CREATE : FLAG_CANCEL_CURRENT;
PendingIntent pi = getActivity(mAppContext, alarm.intId(), intent, flag);
// Even when we try to retrieve a previous instance that actually did exist,
diff --git a/app/src/main/java/com/philliphsu/clock2/util/AlarmUtils.java b/app/src/main/java/com/philliphsu/clock2/util/AlarmUtils.java
index 3a51b3c..980cdb1 100644
--- a/app/src/main/java/com/philliphsu/clock2/util/AlarmUtils.java
+++ b/app/src/main/java/com/philliphsu/clock2/util/AlarmUtils.java
@@ -13,8 +13,8 @@ import com.philliphsu.clock2.Alarm;
import com.philliphsu.clock2.PendingAlarmScheduler;
import com.philliphsu.clock2.R;
import com.philliphsu.clock2.UpcomingAlarmReceiver;
+import com.philliphsu.clock2.alarms.AlarmActivity;
import com.philliphsu.clock2.model.AlarmsTableManager;
-import com.philliphsu.clock2.ringtone.RingtoneActivity;
import com.philliphsu.clock2.ringtone.RingtoneService;
import static android.app.PendingIntent.FLAG_CANCEL_CURRENT;
@@ -180,8 +180,8 @@ public final class AlarmUtils {
private static PendingIntent alarmIntent(Context context, Alarm alarm, boolean retrievePrevious) {
// TODO: Use appropriate subclass instead
- Intent intent = new Intent(context, RingtoneActivity.class)
- .putExtra(RingtoneActivity.EXTRA_ITEM_ID, alarm.id());
+ Intent intent = new Intent(context, AlarmActivity.class)
+ .putExtra(AlarmActivity.EXTRA_ITEM_ID, alarm.id());
int flag = retrievePrevious ? FLAG_NO_CREATE : FLAG_CANCEL_CURRENT;
PendingIntent pi = getActivity(context, alarm.intId(), intent, flag);
// Even when we try to retrieve a previous instance that actually did exist,
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index bdf5af4..47c2014 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -191,4 +191,6 @@
CreateTimerActivity
+
+ Timer