Implement notification actions for timers
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
package com.philliphsu.clock2.timers;
|
||||
|
||||
import com.philliphsu.clock2.AsyncTimersTableUpdateHandler;
|
||||
import com.philliphsu.clock2.Timer;
|
||||
|
||||
/**
|
||||
@@ -7,68 +8,40 @@ import com.philliphsu.clock2.Timer;
|
||||
*/
|
||||
public class TimerController {
|
||||
private final Timer mTimer;
|
||||
private final AsyncTimersTableUpdateHandler mUpdateHandler;
|
||||
|
||||
public TimerController(Timer timer, AsyncTimersTableUpdateHandler updateHandler) {
|
||||
mTimer = timer;
|
||||
mUpdateHandler = updateHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls the appropriate state on the given Timer, based on
|
||||
* its current state.
|
||||
* Start/resume or pause the timer.
|
||||
*/
|
||||
public static void startPause(Timer timer) {
|
||||
if (timer.hasStarted()) {
|
||||
if (timer.isRunning()) {
|
||||
timer.pause();
|
||||
public void startPause() {
|
||||
if (mTimer.hasStarted()) {
|
||||
if (mTimer.isRunning()) {
|
||||
mTimer.pause();
|
||||
} else {
|
||||
timer.resume();
|
||||
mTimer.resume();
|
||||
}
|
||||
} else {
|
||||
timer.start();
|
||||
mTimer.start();
|
||||
}
|
||||
}
|
||||
|
||||
public TimerController(Timer timer) {
|
||||
mTimer = timer;
|
||||
}
|
||||
|
||||
public void start() {
|
||||
mTimer.start();
|
||||
// mChronometer.setBase(mTimer.endTime());
|
||||
// mChronometer.start();
|
||||
// updateStartPauseIcon();
|
||||
// setSecondaryButtonsVisible(true);
|
||||
|
||||
}
|
||||
|
||||
public void pause() {
|
||||
mTimer.pause();
|
||||
// mChronometer.stop();
|
||||
// updateStartPauseIcon();
|
||||
}
|
||||
|
||||
public void resume() {
|
||||
mTimer.resume();
|
||||
// mChronometer.setBase(mTimer.endTime());
|
||||
// mChronometer.start();
|
||||
// updateStartPauseIcon();
|
||||
update();
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
mTimer.stop();
|
||||
// mChronometer.stop();
|
||||
// init();
|
||||
update();
|
||||
}
|
||||
|
||||
public void addOneMinute() {
|
||||
mTimer.addOneMinute();
|
||||
// mChronometer.setBase(mTimer.endTime());
|
||||
update();
|
||||
}
|
||||
|
||||
// public void updateStartPauseIcon() {
|
||||
// // TODO: Pause and start icons, resp.
|
||||
// mStartPause.setImageResource(mTimer.isRunning() ? 0 : 0);
|
||||
// }
|
||||
|
||||
// public void setSecondaryButtonsVisible(boolean visible) {
|
||||
// int visibility = visible ? View.VISIBLE : View.INVISIBLE;
|
||||
// mAddOneMinute.setVisibility(visibility);
|
||||
// mStop.setVisibility(visibility);
|
||||
// }
|
||||
private void update() {
|
||||
mUpdateHandler.asyncUpdate(mTimer.getId(), mTimer);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,54 +1,54 @@
|
||||
package com.philliphsu.clock2.timers;
|
||||
|
||||
import android.app.IntentService;
|
||||
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.annotation.Nullable;
|
||||
import android.support.v4.app.NotificationCompat;
|
||||
|
||||
import com.philliphsu.clock2.AsyncTimersTableUpdateHandler;
|
||||
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.
|
||||
* <p/>
|
||||
* TODO: Customize class - update intent actions, extra parameters and static
|
||||
* helper methods.
|
||||
* Handles the notification for an active Timer.
|
||||
* TOneverDO: extend IntentService, it is ill-suited for our requirement that
|
||||
* this remains alive until we explicitly stop it. Otherwise, it would finish
|
||||
* a single task and immediately destroy itself, which means we lose all of
|
||||
* our instance state.
|
||||
*/
|
||||
public class TimerNotificationService extends IntentService {
|
||||
public class TimerNotificationService extends Service {
|
||||
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";
|
||||
public static final String EXTRA_TIMER = "com.philliphsu.clock2.timers.extra.TIMER";
|
||||
|
||||
private TimersTableManager mTableManager;
|
||||
|
||||
public TimerNotificationService() {
|
||||
super("TimerNotificationService");
|
||||
}
|
||||
private Timer mTimer;
|
||||
private TimerController mController;
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
public static void showNotification(Context context, Timer timer) {
|
||||
Intent intent = new Intent(context, TimerNotificationService.class);
|
||||
intent.putExtra(EXTRA_TIMER_ID, timerId);
|
||||
intent.putExtra(EXTRA_TIMER, timer);
|
||||
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)}.
|
||||
* {@link #showNotification(Context, Timer)}. This does NOT start the Service
|
||||
* and call through to {@link #onStartCommand(Intent, int, int)}, because
|
||||
* the work does not require so.
|
||||
* @param timerId the id of the Timer associated with the notification
|
||||
* you want to cancel
|
||||
*/
|
||||
@@ -59,113 +59,86 @@ public class TimerNotificationService extends IntentService {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
mTableManager = new TimersTableManager(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onHandleIntent(Intent intent) {
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
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);
|
||||
if ((mTimer = intent.getParcelableExtra(EXTRA_TIMER)) == null) {
|
||||
throw new IllegalStateException("Cannot start TimerNotificationService without a Timer");
|
||||
}
|
||||
mController = new TimerController(mTimer, new AsyncTimersTableUpdateHandler(this, null));
|
||||
// TODO: Spawn your own thread to update the countdown text
|
||||
showNotification();
|
||||
} else if (ACTION_ADD_ONE_MINUTE.equals(action)) {
|
||||
handleAddOneMinute(timerId);
|
||||
mController.addOneMinute();
|
||||
// TODO: Verify the notification countdown is extended by one minute.
|
||||
} else if (ACTION_START_PAUSE.equals(action)) {
|
||||
handleStartPause(timerId);
|
||||
mController.startPause();
|
||||
} else if (ACTION_STOP.equals(action)) {
|
||||
handleStop(timerId);
|
||||
mController.stop();
|
||||
stopSelf();
|
||||
// We leave removing the notification up to AsyncTimersTableUpdateHandler
|
||||
// when it calls cancelAlarm() from onPostAsyncUpdate().
|
||||
}
|
||||
}
|
||||
return super.onStartCommand(intent, flags, startId);
|
||||
}
|
||||
|
||||
private void showNotification(long timerId) {
|
||||
Timer timer = getTimer(timerId);
|
||||
@Nullable
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
return null;
|
||||
}
|
||||
|
||||
private void showNotification() {
|
||||
// Base note
|
||||
NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
|
||||
// TODO: correct icon
|
||||
.setSmallIcon(R.drawable.ic_half_day_1_black_24dp)
|
||||
.setSmallIcon(R.drawable.ic_half_day_1_black_24dp) // TODO: correct icon
|
||||
.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);
|
||||
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*/, mTimer.getId());
|
||||
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..
|
||||
// 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();
|
||||
String title = mTimer.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*/);
|
||||
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*/);
|
||||
R.drawable.ic_add_circle_24dp/*TODO: correct icon*/);
|
||||
addAction(builder, ACTION_STOP,
|
||||
timer.getId(), R.drawable.ic_add_circle_24dp/*TODO: correct icon*/);
|
||||
R.drawable.ic_add_circle_24dp/*TODO: correct icon*/);
|
||||
|
||||
NotificationManager nm = (NotificationManager)
|
||||
getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
nm.notify(TAG, timer.getIntId(), builder.build());
|
||||
nm.notify(TAG, mTimer.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) {
|
||||
private void addAction(NotificationCompat.Builder noteBuilder, String action, @DrawableRes int icon) {
|
||||
Intent intent = new Intent(this, TimerNotificationService.class)
|
||||
.setAction(action)
|
||||
.putExtra(EXTRA_TIMER_ID, timerId);
|
||||
.setAction(action);
|
||||
// .putExtra(EXTRA_TIMER, mTimer);
|
||||
PendingIntent pi = PendingIntent.getService(this,
|
||||
(int) timerId, intent, 0/*no flags*/);
|
||||
mTimer.getIntId(), 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package com.philliphsu.clock2.timers;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.provider.Settings;
|
||||
import android.support.v4.app.NotificationCompat;
|
||||
|
||||
import com.philliphsu.clock2.AsyncTimersTableUpdateHandler;
|
||||
import com.philliphsu.clock2.R;
|
||||
import com.philliphsu.clock2.Timer;
|
||||
import com.philliphsu.clock2.ringtone.RingtoneService;
|
||||
|
||||
public class TimerRingtoneService extends RingtoneService<Timer> {
|
||||
|
||||
// private because they refer to our foreground notification's actions.
|
||||
// we reuse these from TimerNotificationService because they're just constants, the values
|
||||
// don't actually matter.
|
||||
private static final String ACTION_ADD_ONE_MINUTE = TimerNotificationService.ACTION_ADD_ONE_MINUTE;
|
||||
private static final String ACTION_STOP = TimerNotificationService.ACTION_STOP;
|
||||
|
||||
private TimerController mController;
|
||||
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
// This has to be first so our Timer is initialized
|
||||
int value = super.onStartCommand(intent, flags, startId);
|
||||
if (mController == null) {
|
||||
mController = new TimerController(getRingingObject(),
|
||||
new AsyncTimersTableUpdateHandler(this, null));
|
||||
}
|
||||
if (intent.getAction() != null) {
|
||||
switch (intent.getAction()) {
|
||||
case ACTION_ADD_ONE_MINUTE:
|
||||
mController.addOneMinute();
|
||||
break;
|
||||
case ACTION_STOP:
|
||||
mController.stop();
|
||||
break;
|
||||
default:
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
stopSelf(startId);
|
||||
finishActivity();
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAutoSilenced() {
|
||||
// TODO: We probably have relevant code to copy over from the old project.
|
||||
// TODO: Stop the Timer and update the table
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Uri getRingtoneUri() {
|
||||
// TODO: Read Timer ringtone preference
|
||||
return Settings.System.DEFAULT_ALARM_ALERT_URI;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Notification getForegroundNotification() {
|
||||
String title = getRingingObject().label();
|
||||
if (title.isEmpty()) {
|
||||
title = getString(R.string.timer);
|
||||
}
|
||||
return new NotificationCompat.Builder(this)
|
||||
.setContentTitle(title)
|
||||
.setContentText(getString(R.string.times_up))
|
||||
.setSmallIcon(R.drawable.ic_half_day_1_black_24dp) // TODO: correct icon
|
||||
.setShowWhen(false) // TODO: Should we show this?
|
||||
// .setOngoing(true) // foreground notes are ongoing by default
|
||||
.addAction(R.drawable.ic_add_circle_24dp, // TODO: correct icon
|
||||
getString(R.string.add_one_minute),
|
||||
getPendingIntent(ACTION_ADD_ONE_MINUTE, getRingingObject().getIntId()))
|
||||
.addAction(R.drawable.ic_add_circle_24dp, // TODO: correct icon
|
||||
getString(R.string.stop),
|
||||
getPendingIntent(ACTION_STOP, getRingingObject().getIntId()))
|
||||
.build();
|
||||
// TODO: .setContentIntent(getPendingIntent(timer.requestCode(), intent, true));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doesVibrate() {
|
||||
// TODO: Create new preference.
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int minutesToAutoSilence() {
|
||||
// TODO: Use same value as for Alarms, or create new preference.
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
@@ -21,8 +21,8 @@ import butterknife.OnClick;
|
||||
public class TimerViewHolder extends BaseViewHolder<Timer> {
|
||||
private static final String TAG = "TimerViewHolder";
|
||||
|
||||
// private TimerController mController;
|
||||
private final AsyncTimersTableUpdateHandler mAsyncTimersTableUpdateHandler;
|
||||
private TimerController mController;
|
||||
|
||||
@Bind(R.id.label) TextView mLabel;
|
||||
@Bind(R.id.duration) CountdownChronometer mChronometer;
|
||||
@@ -31,7 +31,6 @@ public class TimerViewHolder extends BaseViewHolder<Timer> {
|
||||
@Bind(R.id.start_pause) ImageButton mStartPause;
|
||||
@Bind(R.id.stop) ImageButton mStop;
|
||||
|
||||
// TODO: Controller param
|
||||
public TimerViewHolder(ViewGroup parent, OnListItemInteractionListener<Timer> listener,
|
||||
AsyncTimersTableUpdateHandler asyncTimersTableUpdateHandler) {
|
||||
super(parent, R.layout.item_timer, listener);
|
||||
@@ -41,32 +40,26 @@ public class TimerViewHolder extends BaseViewHolder<Timer> {
|
||||
@Override
|
||||
public void onBind(Timer timer) {
|
||||
super.onBind(timer);
|
||||
// TOneverDO: create before super
|
||||
mController = new TimerController(timer, mAsyncTimersTableUpdateHandler);
|
||||
bindLabel(timer.label());
|
||||
// // We can't create the controller until this VH binds, because
|
||||
// // the widgets only exist after this point.
|
||||
// mController = new TimerController(timer, mChronometer, mAddOneMinute, mStartPause, mStop);
|
||||
bindChronometer(timer);
|
||||
bindButtonControls(timer);
|
||||
}
|
||||
|
||||
@OnClick(R.id.start_pause)
|
||||
void startPause() {
|
||||
TimerController.startPause(getItem());
|
||||
// Persist value changes
|
||||
update();
|
||||
mController.startPause();
|
||||
}
|
||||
|
||||
@OnClick(R.id.add_one_minute)
|
||||
void addOneMinute() {
|
||||
getItem().addOneMinute();
|
||||
// Persist end time increase
|
||||
update();
|
||||
mController.addOneMinute();
|
||||
}
|
||||
|
||||
@OnClick(R.id.stop)
|
||||
void stop() {
|
||||
getItem().stop();
|
||||
update();
|
||||
mController.stop();
|
||||
}
|
||||
|
||||
private void bindLabel(String label) {
|
||||
@@ -113,13 +106,4 @@ public class TimerViewHolder extends BaseViewHolder<Timer> {
|
||||
mAddOneMinute.setVisibility(visibility);
|
||||
mStop.setVisibility(visibility);
|
||||
}
|
||||
|
||||
private void update() {
|
||||
Timer t = getItem();
|
||||
mAsyncTimersTableUpdateHandler.asyncUpdate(
|
||||
// Alternatively, use ViewHolder#getItemId() because we can forget
|
||||
// to set the id on the object in BaseItemCursor#getItem(). We
|
||||
// luckily remembered to this time!
|
||||
t.getId(), t);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,8 @@ public class TimersFragment extends RecyclerViewFragment<
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
if (resultCode != Activity.RESULT_OK || data == null)
|
||||
return;
|
||||
// TODO: From EditTimerActivity, pass back the Timer as a parcelable and
|
||||
// retrieve it here directly.
|
||||
int hour = data.getIntExtra(EditTimerActivity.EXTRA_HOUR, -1);
|
||||
int minute = data.getIntExtra(EditTimerActivity.EXTRA_MINUTE, -1);
|
||||
int second = data.getIntExtra(EditTimerActivity.EXTRA_SECOND, -1);
|
||||
|
||||
@@ -1,22 +1,29 @@
|
||||
package com.philliphsu.clock2.timers;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
|
||||
public class TimesUpActivity extends AppCompatActivity {
|
||||
import com.philliphsu.clock2.R;
|
||||
import com.philliphsu.clock2.Timer;
|
||||
import com.philliphsu.clock2.ringtone.RingtoneActivity;
|
||||
import com.philliphsu.clock2.ringtone.RingtoneService;
|
||||
|
||||
public class TimesUpActivity extends RingtoneActivity<Timer> {
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
stopService(new Intent(this, TimerNotificationService.class));
|
||||
TimerNotificationService.cancelNotification(this, getRingingObject().getId());
|
||||
}
|
||||
|
||||
// @Override
|
||||
// public Loader<Timer> onCreateLoader(long itemId) {
|
||||
// return new TimerLoader(this, itemId);
|
||||
// }
|
||||
@Override
|
||||
public int layoutResource() {
|
||||
return R.layout.activity_ringtone;
|
||||
}
|
||||
|
||||
// @Override
|
||||
// public int layoutResource() {
|
||||
// return R.layout.activity_ringtone;
|
||||
// }
|
||||
@Override
|
||||
protected Class<? extends RingtoneService> getRingtoneServiceClass() {
|
||||
return TimerRingtoneService.class;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user