Implement notification actions for timers

This commit is contained in:
Phillip Hsu 2016-08-05 01:06:26 -07:00
parent 25c544df43
commit 8dea6301aa
16 changed files with 312 additions and 299 deletions

View File

@ -31,11 +31,14 @@
</activity> </activity>
--> -->
<!--<service
<!--
<service
android:name=".ringtone.RingtoneService" android:name=".ringtone.RingtoneService"
android:enabled="true" android:enabled="true"
android:exported="false"> android:exported="false">
</service>--> </service>
-->
<receiver <receiver
android:name=".UpcomingAlarmReceiver" android:name=".UpcomingAlarmReceiver"
@ -116,6 +119,11 @@
android:enabled="true" android:enabled="true"
android:exported="false"> android:exported="false">
</service> </service>
<service
android:name=".timers.TimerRingtoneService"
android:enabled="true"
android:exported="false">
</service>
</application> </application>
</manifest> </manifest>

View File

@ -52,6 +52,7 @@ public final class AsyncTimersTableUpdateHandler extends AsyncDatabaseTableUpdat
private PendingIntent createTimesUpIntent(Timer timer) { private PendingIntent createTimesUpIntent(Timer timer) {
Intent intent = new Intent(getContext(), TimesUpActivity.class); Intent intent = new Intent(getContext(), TimesUpActivity.class);
// intent.putExtra(TimesUpActivity.EXTRA_ITEM_ID, timer.getId()); // intent.putExtra(TimesUpActivity.EXTRA_ITEM_ID, timer.getId());
intent.putExtra(TimesUpActivity.EXTRA_RINGING_OBJECT, timer);
// There's no point to determining whether to retrieve a previous instance, because // 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 // 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 // were caused by you using the same reference variable for every Intent/PI that
@ -63,7 +64,7 @@ public final class AsyncTimersTableUpdateHandler extends AsyncDatabaseTableUpdat
private void scheduleAlarm(Timer timer) { private void scheduleAlarm(Timer timer) {
AlarmManager am = (AlarmManager) getContext().getSystemService(Context.ALARM_SERVICE); AlarmManager am = (AlarmManager) getContext().getSystemService(Context.ALARM_SERVICE);
am.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, timer.endTime(), createTimesUpIntent(timer)); am.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, timer.endTime(), createTimesUpIntent(timer));
TimerNotificationService.showNotification(getContext(), timer.getId()); TimerNotificationService.showNotification(getContext(), timer);
} }
private void cancelAlarm(Timer timer, boolean removeNotification) { private void cancelAlarm(Timer timer, boolean removeNotification) {

View File

@ -1,5 +1,7 @@
package com.philliphsu.clock2; package com.philliphsu.clock2;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.SystemClock; import android.os.SystemClock;
import com.google.auto.value.AutoValue; import com.google.auto.value.AutoValue;
@ -11,7 +13,7 @@ import java.util.concurrent.TimeUnit;
* Created by Phillip Hsu on 7/25/2016. * Created by Phillip Hsu on 7/25/2016.
*/ */
@AutoValue @AutoValue
public abstract class Timer extends ObjectWithId /*implements Parcelable*/ { public abstract class Timer extends ObjectWithId implements Parcelable {
private static final long MINUTE = TimeUnit.MINUTES.toMillis(1); private static final long MINUTE = TimeUnit.MINUTES.toMillis(1);
private long endTime; private long endTime;
@ -129,17 +131,41 @@ public abstract class Timer extends ObjectWithId /*implements Parcelable*/ {
return pauseTime; return pauseTime;
} }
// @Override @Override
// public int describeContents() { public int describeContents() {
// return 0; return 0;
// } }
//
// @Override @Override
// public void writeToParcel(Parcel dest, int flags) { public void writeToParcel(Parcel dest, int flags) {
// dest.writeInt(hour()); dest.writeInt(hour());
// dest.writeInt(minute()); dest.writeInt(minute());
// dest.writeInt(second()); dest.writeInt(second());
// dest.writeString(group()); dest.writeString(group());
// dest.writeString(label()); dest.writeString(label());
// } dest.writeLong(getId());
dest.writeLong(endTime);
dest.writeLong(pauseTime);
}
public static final Creator<Timer> CREATOR = new Creator<Timer>() {
@Override
public Timer createFromParcel(Parcel source) {
return Timer.create(source);
}
@Override
public Timer[] newArray(int size) {
return new Timer[size];
}
};
private static Timer create(Parcel source) {
Timer t = Timer.create(source.readInt(), source.readInt(), source.readInt(),
source.readString(), source.readString());
t.setId(source.readLong());
t.endTime = source.readLong();
t.pauseTime = source.readLong();
return t;
}
} }

View File

@ -13,19 +13,14 @@ import com.philliphsu.clock2.util.AlarmController;
public class AlarmActivity extends RingtoneActivity<Alarm> { public class AlarmActivity extends RingtoneActivity<Alarm> {
private AlarmController mAlarmController; private AlarmController mAlarmController;
// TODO: Write a getter method instead in the base class?
private Alarm mAlarm;
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
if ((mAlarm = getIntent().getParcelableExtra(EXTRA_ITEM)) == null) {
throw new IllegalStateException("Cannot start AlarmActivity without an Alarm");
}
mAlarmController = new AlarmController(this, null); mAlarmController = new AlarmController(this, null);
// TODO: If the upcoming alarm notification isn't present, verify other notifications aren't affected. // 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. // This could be the case if we're starting a new instance of this activity after leaving the first launch.
mAlarmController.removeUpcomingAlarmNotification(mAlarm); mAlarmController.removeUpcomingAlarmNotification(getRingingObject());
// TODO: Butterknife binding // TODO: Butterknife binding
Button snooze = (Button) findViewById(R.id.btn_snooze); Button snooze = (Button) findViewById(R.id.btn_snooze);
snooze.setOnClickListener(new View.OnClickListener() { snooze.setOnClickListener(new View.OnClickListener() {
@ -43,22 +38,6 @@ public class AlarmActivity extends RingtoneActivity<Alarm> {
}); });
} }
// @Override
// public Loader<Alarm> onCreateLoader(long id) {
// return new AlarmLoader(this, id);
// }
//
// @Override
// public void onLoadFinished(Loader<Alarm> 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 @Override
public int layoutResource() { public int layoutResource() {
return R.layout.activity_ringtone; return R.layout.activity_ringtone;
@ -70,19 +49,15 @@ public class AlarmActivity extends RingtoneActivity<Alarm> {
} }
private void snooze() { private void snooze() {
if (mAlarm != null) { mAlarmController.snoozeAlarm(getRingingObject());
mAlarmController.snoozeAlarm(mAlarm);
}
// Can't call dismiss() because we don't want to also call cancelAlarm()! Why? For example, // 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. // we don't want the alarm, if it has no recurrence, to be turned off right now.
stopAndFinish(); stopAndFinish();
} }
private void dismiss() { private void dismiss() {
if (mAlarm != null) { // TODO do we really need to cancel the intent and alarm?
// TODO do we really need to cancel the intent and alarm? mAlarmController.cancelAlarm(getRingingObject(), false);
mAlarmController.cancelAlarm(mAlarm, false);
}
stopAndFinish(); stopAndFinish();
} }
} }

View File

@ -3,8 +3,6 @@ package com.philliphsu.clock2.alarms;
import android.app.Notification; import android.app.Notification;
import android.app.NotificationManager; import android.app.NotificationManager;
import android.content.Intent; import android.content.Intent;
import android.media.Ringtone;
import android.media.RingtoneManager;
import android.net.Uri; import android.net.Uri;
import android.support.v4.app.NotificationCompat; import android.support.v4.app.NotificationCompat;
@ -24,41 +22,16 @@ public class AlarmRingtoneService extends RingtoneService<Alarm> {
private String mNormalRingTime; private String mNormalRingTime;
private AlarmController mAlarmController; private AlarmController mAlarmController;
private Alarm mAlarm;
@Override @Override
public int onStartCommand(Intent intent, int flags, int startId) { public int onStartCommand(Intent intent, int flags, int startId) {
// TOneverDO: Call super before our custom logic // We can have this before super because this will only call through
if (intent.getAction() == null) { // WHILE this Service has already been alive.
// final long id = intent.getLongExtra(EXTRA_ITEM_ID, -1); if (intent.getAction() != null) {
// if (id < 0)
// throw new IllegalStateException("No item id set");
// http://stackoverflow.com/q/8696146/5055032
// Start our own thread to load the alarm instead of using a loader,
// because Services do not have a built-in LoaderManager (because they have no need for one since
// their lifecycle is not complex like in Activities/Fragments) and our
// work is simple enough that getting loaders to work here is not
// worth the effort.
// // TODO: Will using the Runnable like this cause a memory leak?
// new Thread(new Runnable() {
// @Override
// public void run() {
// // TODO: We don't actually need the exact same Alarm instance as the
// // one from our calling component, because we won't mutate any of its
// // fields. Since we only read values, we could just pass in the Alarm
// // to the intent as a Parcelable.
// AlarmCursor cursor = new AlarmsTableManager(AlarmRingtoneService.this).queryItem(id);
// mAlarm = checkNotNull(cursor.getItem());
// }
// }).start();
if ((mAlarm = intent.getParcelableExtra(EXTRA_ITEM)) == null) {
throw new IllegalStateException("Cannot start AlarmRingtoneService without an Alarm");
}
} else {
if (ACTION_SNOOZE.equals(intent.getAction())) { if (ACTION_SNOOZE.equals(intent.getAction())) {
mAlarmController.snoozeAlarm(mAlarm); mAlarmController.snoozeAlarm(getRingingObject());
} else if (ACTION_DISMISS.equals(intent.getAction())) { } else if (ACTION_DISMISS.equals(intent.getAction())) {
mAlarmController.cancelAlarm(mAlarm, false); // TODO do we really need to cancel the intent and alarm? mAlarmController.cancelAlarm(getRingingObject(), false); // TODO do we really need to cancel the intent and alarm?
} else { } else {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
@ -78,7 +51,7 @@ public class AlarmRingtoneService extends RingtoneService<Alarm> {
@Override @Override
protected void onAutoSilenced() { protected void onAutoSilenced() {
// TODO do we really need to cancel the alarm and intent? // TODO do we really need to cancel the alarm and intent?
mAlarmController.cancelAlarm(mAlarm, false); mAlarmController.cancelAlarm(getRingingObject(), false);
// Post notification that alarm was missed // Post notification that alarm was missed
NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
Notification note = new NotificationCompat.Builder(this) Notification note = new NotificationCompat.Builder(this)
@ -86,38 +59,37 @@ public class AlarmRingtoneService extends RingtoneService<Alarm> {
.setContentText(mNormalRingTime) .setContentText(mNormalRingTime)
.setSmallIcon(R.mipmap.ic_launcher) .setSmallIcon(R.mipmap.ic_launcher)
.build(); .build();
nm.notify(TAG, mAlarm.intId(), note); nm.notify(TAG, getRingingObject().intId(), note);
} }
@Override @Override
protected Ringtone getRingtone() { protected Uri getRingtoneUri() {
Uri ringtone = Uri.parse(mAlarm.ringtone()); return Uri.parse(getRingingObject().ringtone());
return RingtoneManager.getRingtone(this, ringtone);
} }
@Override @Override
protected Notification getForegroundNotification() { protected Notification getForegroundNotification() {
String title = mAlarm.label().isEmpty() String title = getRingingObject().label().isEmpty()
? getString(R.string.alarm) ? getString(R.string.alarm)
: mAlarm.label(); : getRingingObject().label();
mNormalRingTime = formatTime(this, System.currentTimeMillis()); // now mNormalRingTime = formatTime(this, System.currentTimeMillis()); // now
return new NotificationCompat.Builder(this) return new NotificationCompat.Builder(this)
// Required contents // Required contents
.setSmallIcon(R.mipmap.ic_launcher) // TODO: alarm icon .setSmallIcon(R.mipmap.ic_launcher) // TODO: alarm icon
.setContentTitle(title) .setContentTitle(title)
.setContentText(mNormalRingTime) .setContentText(mNormalRingTime)
.addAction(R.mipmap.ic_launcher, .addAction(R.mipmap.ic_launcher, // TODO: correct icon
getString(R.string.snooze), getString(R.string.snooze),
getPendingIntent(ACTION_SNOOZE, mAlarm)) getPendingIntent(ACTION_SNOOZE, getRingingObject().getIntId()))
.addAction(R.mipmap.ic_launcher, .addAction(R.mipmap.ic_launcher, // TODO: correct icon
getString(R.string.dismiss), getString(R.string.dismiss),
getPendingIntent(ACTION_DISMISS, mAlarm)) getPendingIntent(ACTION_DISMISS, getRingingObject().getIntId()))
.build(); .build();
} }
@Override @Override
protected boolean doesVibrate() { protected boolean doesVibrate() {
return mAlarm.vibrates(); return getRingingObject().vibrates();
} }
@Override @Override

View File

@ -16,20 +16,15 @@ import com.philliphsu.clock2.util.LocalBroadcastHelper;
* An example full-screen activity that shows and hides the system UI (i.e. * An example full-screen activity that shows and hides the system UI (i.e.
* status bar and navigation/system bar) with user interaction. * status bar and navigation/system bar) with user interaction.
*/ */
public abstract class RingtoneActivity<T extends Parcelable> extends AppCompatActivity /*implements LoaderCallbacks<T>*/ { public abstract class RingtoneActivity<T extends Parcelable> extends AppCompatActivity {
private static final String TAG = "RingtoneActivity"; private static final String TAG = "RingtoneActivity";
// Shared with RingtoneService // Shared with RingtoneService
// public static final String EXTRA_ITEM_ID = "com.philliphsu.clock2.ringtone.extra.ITEM_ID";
public static final String ACTION_FINISH = "com.philliphsu.clock2.ringtone.action.FINISH"; public static final String ACTION_FINISH = "com.philliphsu.clock2.ringtone.action.FINISH";
public static final String EXTRA_ITEM = "com.philliphsu.clock2.ringtone.extra.ITEM"; public static final String EXTRA_RINGING_OBJECT = "com.philliphsu.clock2.ringtone.extra.RINGING_OBJECT";
private static boolean sIsAlive = false; private static boolean sIsAlive = false;
private T mRingingObject;
// private long mItemId;
// private T mItem;
// public abstract Loader<T> onCreateLoader(long itemId);
// TODO: Should we extend from BaseActivity instead? // TODO: Should we extend from BaseActivity instead?
@LayoutRes @LayoutRes
@ -48,17 +43,11 @@ public abstract class RingtoneActivity<T extends Parcelable> extends AppCompatAc
getWindow().addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD); getWindow().addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED); getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
// mItemId = getIntent().getLongExtra(EXTRA_ITEM_ID, -1); if ((mRingingObject = getIntent().getParcelableExtra(EXTRA_RINGING_OBJECT)) == null) {
// if (mItemId < 0) { throw new IllegalStateException("Cannot start RingtoneActivity without a ringing object");
// 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
// Activity, which has complex lifecycle. LoaderManager is designed to help
// us through the vagaries of the lifecycle that could affect loading data.
// getSupportLoaderManager().initLoader(0, null, this);
Intent intent = new Intent(this, getRingtoneServiceClass()) Intent intent = new Intent(this, getRingtoneServiceClass())
.putExtra(EXTRA_ITEM, getIntent().getParcelableExtra(EXTRA_ITEM)); .putExtra(EXTRA_RINGING_OBJECT, mRingingObject);
startService(intent); startService(intent);
} }
@ -126,21 +115,6 @@ public abstract class RingtoneActivity<T extends Parcelable> extends AppCompatAc
sIsAlive = false; sIsAlive = false;
} }
// @Override
// public Loader<T> onCreateLoader(int id, Bundle args) {
// return onCreateLoader(mItemId);
// }
//
// @Override
// public void onLoadFinished(Loader<T> loader, T data) {
// mItem = data;
// }
//
// @Override
// public void onLoaderReset(Loader<T> loader) {
// // Do nothing
// }
public static boolean isAlive() { public static boolean isAlive() {
return sIsAlive; return sIsAlive;
} }
@ -154,6 +128,10 @@ public abstract class RingtoneActivity<T extends Parcelable> extends AppCompatAc
finish(); finish();
} }
protected final T getRingingObject() {
return mRingingObject;
}
// TODO: Do we need this anymore? I think this broadcast was only sent from // TODO: Do we need this anymore? I think this broadcast was only sent from
// EditAlarmActivity? // EditAlarmActivity?
private final BroadcastReceiver mFinishReceiver = new BroadcastReceiver() { private final BroadcastReceiver mFinishReceiver = new BroadcastReceiver() {

View File

@ -6,14 +6,16 @@ import android.app.Service;
import android.content.Intent; import android.content.Intent;
import android.media.AudioManager; import android.media.AudioManager;
import android.media.Ringtone; import android.media.Ringtone;
import android.media.RingtoneManager;
import android.net.Uri;
import android.os.Handler; import android.os.Handler;
import android.os.IBinder; import android.os.IBinder;
import android.os.Parcelable;
import android.os.Vibrator; import android.os.Vibrator;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.util.Log; import android.util.Log;
import com.philliphsu.clock2.Alarm;
import com.philliphsu.clock2.R; import com.philliphsu.clock2.R;
import com.philliphsu.clock2.util.LocalBroadcastHelper; import com.philliphsu.clock2.util.LocalBroadcastHelper;
@ -29,18 +31,18 @@ import java.util.concurrent.TimeUnit;
* *
* TOneverDO: Change this to not be a started service! * TOneverDO: Change this to not be a started service!
*/ */
// TODO: Remove this from manifest, keep only the subclasses. public abstract class RingtoneService<T extends Parcelable> extends Service {
public abstract class RingtoneService<T> extends Service {
private static final String TAG = "RingtoneService"; private static final String TAG = "RingtoneService";
// public okay // public okay
public static final String ACTION_NOTIFY_MISSED = "com.philliphsu.clock2.ringtone.action.NOTIFY_MISSED"; public static final String ACTION_NOTIFY_MISSED = "com.philliphsu.clock2.ringtone.action.NOTIFY_MISSED";
// public static final String EXTRA_ITEM_ID = RingtoneActivity.EXTRA_ITEM_ID; // public static final String EXTRA_ITEM_ID = RingtoneActivity.EXTRA_ITEM_ID;
public static final String EXTRA_ITEM = RingtoneActivity.EXTRA_ITEM; public static final String EXTRA_RINGING_OBJECT = RingtoneActivity.EXTRA_RINGING_OBJECT;
private AudioManager mAudioManager; private AudioManager mAudioManager;
private Ringtone mRingtone; private Ringtone mRingtone;
@Nullable private Vibrator mVibrator; @Nullable private Vibrator mVibrator;
private T mRingingObject;
// TODO: Using Handler for this is ill-suited? Alarm ringing could outlast the // TODO: Using Handler for this is ill-suited? Alarm ringing could outlast the
// application's life. Use AlarmManager API instead. // application's life. Use AlarmManager API instead.
@ -57,7 +59,8 @@ public abstract class RingtoneService<T> extends Service {
} }
}; };
// Pretty sure we don't need this anymore... // Pretty sure this won't ever get called anymore... b/c EditAlarmActivity, the only component
// that sends such a broadcast, is deprecated.
// private final BroadcastReceiver mNotifyMissedReceiver = new BroadcastReceiver() { // private final BroadcastReceiver mNotifyMissedReceiver = new BroadcastReceiver() {
// @Override // @Override
// public void onReceive(Context context, Intent intent) { // public void onReceive(Context context, Intent intent) {
@ -68,9 +71,13 @@ public abstract class RingtoneService<T> extends Service {
// } // }
// }; // };
/**
* Callback invoked when this Service is stopping and the corresponding
* {@link RingtoneActivity} is finishing.
*/
protected abstract void onAutoSilenced(); protected abstract void onAutoSilenced();
protected abstract Ringtone getRingtone(); protected abstract Uri getRingtoneUri();
/** /**
* @return the notification to show when this Service starts in the foreground * @return the notification to show when this Service starts in the foreground
@ -86,6 +93,11 @@ public abstract class RingtoneService<T> extends Service {
@Override @Override
public int onStartCommand(Intent intent, int flags, int startId) { public int onStartCommand(Intent intent, int flags, int startId) {
if (mRingingObject == null) {
if ((mRingingObject = intent.getParcelableExtra(EXTRA_RINGING_OBJECT)) == null) {
throw new IllegalStateException("Cannot start RingtoneService without a ringing object");
}
}
// Play ringtone, if not already playing // Play ringtone, if not already playing
if (mAudioManager == null && mRingtone == null) { if (mAudioManager == null && mRingtone == null) {
// TOneverDO: Pass 0 as the first argument // TOneverDO: Pass 0 as the first argument
@ -100,7 +112,7 @@ public abstract class RingtoneService<T> extends Service {
// Request permanent focus, as ringing could last several minutes // Request permanent focus, as ringing could last several minutes
AudioManager.AUDIOFOCUS_GAIN); AudioManager.AUDIOFOCUS_GAIN);
if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) { if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
mRingtone = getRingtone(); mRingtone = RingtoneManager.getRingtone(this, getRingtoneUri());
// Deprecated, but the alternative AudioAttributes requires API 21 // Deprecated, but the alternative AudioAttributes requires API 21
mRingtone.setStreamType(AudioManager.STREAM_ALARM); mRingtone.setStreamType(AudioManager.STREAM_ALARM);
mRingtone.play(); mRingtone.play();
@ -125,7 +137,8 @@ public abstract class RingtoneService<T> extends Service {
@Override @Override
public void onCreate() { public void onCreate() {
super.onCreate(); super.onCreate();
// Pretty sure this won't ever get called anymore... // Pretty sure this won't ever get called anymore... b/c EditAlarmActivity, the only component
// that sends such a broadcast, is deprecated.
// LocalBroadcastHelper.registerReceiver(this, mNotifyMissedReceiver, ACTION_NOTIFY_MISSED); // LocalBroadcastHelper.registerReceiver(this, mNotifyMissedReceiver, ACTION_NOTIFY_MISSED);
} }
@ -139,6 +152,8 @@ public abstract class RingtoneService<T> extends Service {
} }
mSilenceHandler.removeCallbacks(mSilenceRunnable); mSilenceHandler.removeCallbacks(mSilenceRunnable);
stopForeground(true); stopForeground(true);
// Pretty sure this won't ever get called anymore... b/c EditAlarmActivity, the only component
// that sends such a broadcast, is deprecated.
// LocalBroadcastHelper.unregisterReceiver(this, mNotifyMissedReceiver); // LocalBroadcastHelper.unregisterReceiver(this, mNotifyMissedReceiver);
} }
@ -166,16 +181,17 @@ public abstract class RingtoneService<T> extends Service {
/** /**
* Exposed so subclasses can create their notification actions. * Exposed so subclasses can create their notification actions.
*/ */
// TODO: Consider changing Alarm param to int requestCode param. protected final PendingIntent getPendingIntent(@NonNull String action, int requestCode) {
protected final PendingIntent getPendingIntent(@NonNull String action, Alarm alarm) {
Intent intent = new Intent(this, getClass()) Intent intent = new Intent(this, getClass())
.setAction(action); .setAction(action);
// TODO: Why do we need this?
// .putExtra(EXTRA_ITEM_ID, alarm.id());
return PendingIntent.getService( return PendingIntent.getService(
this, this,
alarm.intId(), requestCode,
intent, intent,
PendingIntent.FLAG_ONE_SHOT); PendingIntent.FLAG_ONE_SHOT);
} }
protected final T getRingingObject() {
return mRingingObject;
}
} }

View File

@ -1,5 +1,6 @@
package com.philliphsu.clock2.timers; package com.philliphsu.clock2.timers;
import com.philliphsu.clock2.AsyncTimersTableUpdateHandler;
import com.philliphsu.clock2.Timer; import com.philliphsu.clock2.Timer;
/** /**
@ -7,68 +8,40 @@ import com.philliphsu.clock2.Timer;
*/ */
public class TimerController { public class TimerController {
private final Timer mTimer; 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 * Start/resume or pause the timer.
* its current state.
*/ */
public static void startPause(Timer timer) { public void startPause() {
if (timer.hasStarted()) { if (mTimer.hasStarted()) {
if (timer.isRunning()) { if (mTimer.isRunning()) {
timer.pause(); mTimer.pause();
} else { } else {
timer.resume(); mTimer.resume();
} }
} else { } else {
timer.start(); mTimer.start();
} }
} update();
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();
} }
public void stop() { public void stop() {
mTimer.stop(); mTimer.stop();
// mChronometer.stop(); update();
// init();
} }
public void addOneMinute() { public void addOneMinute() {
mTimer.addOneMinute(); mTimer.addOneMinute();
// mChronometer.setBase(mTimer.endTime()); update();
} }
// public void updateStartPauseIcon() { private void update() {
// // TODO: Pause and start icons, resp. mUpdateHandler.asyncUpdate(mTimer.getId(), mTimer);
// mStartPause.setImageResource(mTimer.isRunning() ? 0 : 0); }
// }
// public void setSecondaryButtonsVisible(boolean visible) {
// int visibility = visible ? View.VISIBLE : View.INVISIBLE;
// mAddOneMinute.setVisibility(visibility);
// mStop.setVisibility(visibility);
// }
} }

View File

@ -1,54 +1,54 @@
package com.philliphsu.clock2.timers; package com.philliphsu.clock2.timers;
import android.app.IntentService;
import android.app.NotificationManager; import android.app.NotificationManager;
import android.app.PendingIntent; import android.app.PendingIntent;
import android.app.Service;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.os.IBinder;
import android.support.annotation.DrawableRes; import android.support.annotation.DrawableRes;
import android.support.annotation.Nullable;
import android.support.v4.app.NotificationCompat; import android.support.v4.app.NotificationCompat;
import com.philliphsu.clock2.AsyncTimersTableUpdateHandler;
import com.philliphsu.clock2.MainActivity; import com.philliphsu.clock2.MainActivity;
import com.philliphsu.clock2.R; import com.philliphsu.clock2.R;
import com.philliphsu.clock2.Timer; import com.philliphsu.clock2.Timer;
import com.philliphsu.clock2.model.TimersTableManager;
/** /**
* An {@link IntentService} subclass for handling asynchronous task requests in * Handles the notification for an active Timer.
* a service on a separate handler thread. * TOneverDO: extend IntentService, it is ill-suited for our requirement that
* <p/> * this remains alive until we explicitly stop it. Otherwise, it would finish
* TODO: Customize class - update intent actions, extra parameters and static * a single task and immediately destroy itself, which means we lose all of
* helper methods. * our instance state.
*/ */
public class TimerNotificationService extends IntentService { public class TimerNotificationService extends Service {
private static final String TAG = "TimerNotificationService"; 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_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_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 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; private Timer mTimer;
private TimerController mController;
public TimerNotificationService() {
super("TimerNotificationService");
}
/** /**
* Helper method to start this Service for its default action: to show * Helper method to start this Service for its default action: to show
* the notification for the Timer with the given id. * 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 intent = new Intent(context, TimerNotificationService.class);
intent.putExtra(EXTRA_TIMER_ID, timerId); intent.putExtra(EXTRA_TIMER, timer);
context.startService(intent); context.startService(intent);
} }
/** /**
* Helper method to cancel the notification previously shown from calling * Helper method to cancel the notification previously shown from calling
* {@link #showNotification(Context, long)}. This does NOT start the Service * {@link #showNotification(Context, Timer)}. This does NOT start the Service
* and call through to {@link #onHandleIntent(Intent)}. * 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 * @param timerId the id of the Timer associated with the notification
* you want to cancel * you want to cancel
*/ */
@ -59,113 +59,86 @@ public class TimerNotificationService extends IntentService {
} }
@Override @Override
public void onCreate() { public int onStartCommand(Intent intent, int flags, int startId) {
super.onCreate();
mTableManager = new TimersTableManager(this);
}
@Override
protected void onHandleIntent(Intent intent) {
if (intent != null) { 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(); final String action = intent.getAction();
if (action == null) { 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)) { } 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)) { } else if (ACTION_START_PAUSE.equals(action)) {
handleStartPause(timerId); mController.startPause();
} else if (ACTION_STOP.equals(action)) { } 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) { @Nullable
Timer timer = getTimer(timerId); @Override
public IBinder onBind(Intent intent) {
return null;
}
private void showNotification() {
// Base note // Base note
NotificationCompat.Builder builder = new NotificationCompat.Builder(this) NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
// TODO: correct icon .setSmallIcon(R.drawable.ic_half_day_1_black_24dp) // TODO: correct icon
.setSmallIcon(R.drawable.ic_half_day_1_black_24dp)
.setShowWhen(false) .setShowWhen(false)
.setOngoing(true); .setOngoing(true);
// TODO: Set content intent so that when clicked, we launch // TODO: Set content intent so that when clicked, we launch
// TimersFragment and scroll to the given timer id. The following // TimersFragment and scroll to the given timer id. The following
// is merely pseudocode. // is merely pseudocode.
Intent contentIntent = new Intent(this, MainActivity.class); Intent contentIntent = new Intent(this, MainActivity.class);
contentIntent.putExtra(null/*TODO:MainActivity.EXTRA_SHOW_PAGE*/, contentIntent.putExtra(null/*TODO:MainActivity.EXTRA_SHOW_PAGE*/, 1/*TODO:The tab index of the timers page*/);
1/*TODO:The tab index of the timers page*/); contentIntent.putExtra(null/*TODO:MainActivity.EXTRA_SCROLL_TO_ID*/, mTimer.getId());
contentIntent.putExtra(null/*TODO:MainActivity.EXTRA_SCROLL_TO_ID*/,
timerId);
builder.setContentIntent(PendingIntent.getActivity( builder.setContentIntent(PendingIntent.getActivity(
this, this,
0, // TODO: Request code not needed? Since any multiple notifications 0, // TODO: Request code not needed? Since any multiple notifications
// should be able to use the same PendingIntent for this action.... // should be able to use the same PendingIntent for this action....
// unless the underlying *Intent* and its id extra are overwritten // unless the underlying *Intent* and its id extra are overwritten
// per notification when retrieving the PendingIntent.. // per notification when retrieving the PendingIntent..
contentIntent, contentIntent,
0/*Shouldn't need a flag..*/)); 0/*Shouldn't need a flag..*/));
// TODO: Use a handler to continually update the countdown text // TODO: Use a handler to continually update the countdown text
String title = timer.label(); String title = mTimer.label();
if (title.isEmpty()) { if (title.isEmpty()) {
title = getString(R.string.timer); title = getString(R.string.timer);
} }
builder.setContentTitle(title); builder.setContentTitle(title);
addAction(builder, ACTION_ADD_ONE_MINUTE, 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, 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, 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) NotificationManager nm = (NotificationManager)
getSystemService(Context.NOTIFICATION_SERVICE); 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. * Builds and adds the specified action to the notification's builder.
*/ */
private void addAction(NotificationCompat.Builder noteBuilder, String action, private void addAction(NotificationCompat.Builder noteBuilder, String action, @DrawableRes int icon) {
long timerId, @DrawableRes int icon) {
Intent intent = new Intent(this, TimerNotificationService.class) Intent intent = new Intent(this, TimerNotificationService.class)
.setAction(action) .setAction(action);
.putExtra(EXTRA_TIMER_ID, timerId); // .putExtra(EXTRA_TIMER, mTimer);
PendingIntent pi = PendingIntent.getService(this, PendingIntent pi = PendingIntent.getService(this,
(int) timerId, intent, 0/*no flags*/); mTimer.getIntId(), intent, 0/*no flags*/);
noteBuilder.addAction(icon, ""/*no action title*/, pi); 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();
}
}

View File

@ -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;
}
}

View File

@ -21,8 +21,8 @@ import butterknife.OnClick;
public class TimerViewHolder extends BaseViewHolder<Timer> { public class TimerViewHolder extends BaseViewHolder<Timer> {
private static final String TAG = "TimerViewHolder"; private static final String TAG = "TimerViewHolder";
// private TimerController mController;
private final AsyncTimersTableUpdateHandler mAsyncTimersTableUpdateHandler; private final AsyncTimersTableUpdateHandler mAsyncTimersTableUpdateHandler;
private TimerController mController;
@Bind(R.id.label) TextView mLabel; @Bind(R.id.label) TextView mLabel;
@Bind(R.id.duration) CountdownChronometer mChronometer; @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.start_pause) ImageButton mStartPause;
@Bind(R.id.stop) ImageButton mStop; @Bind(R.id.stop) ImageButton mStop;
// TODO: Controller param
public TimerViewHolder(ViewGroup parent, OnListItemInteractionListener<Timer> listener, public TimerViewHolder(ViewGroup parent, OnListItemInteractionListener<Timer> listener,
AsyncTimersTableUpdateHandler asyncTimersTableUpdateHandler) { AsyncTimersTableUpdateHandler asyncTimersTableUpdateHandler) {
super(parent, R.layout.item_timer, listener); super(parent, R.layout.item_timer, listener);
@ -41,32 +40,26 @@ public class TimerViewHolder extends BaseViewHolder<Timer> {
@Override @Override
public void onBind(Timer timer) { public void onBind(Timer timer) {
super.onBind(timer); super.onBind(timer);
// TOneverDO: create before super
mController = new TimerController(timer, mAsyncTimersTableUpdateHandler);
bindLabel(timer.label()); 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); bindChronometer(timer);
bindButtonControls(timer); bindButtonControls(timer);
} }
@OnClick(R.id.start_pause) @OnClick(R.id.start_pause)
void startPause() { void startPause() {
TimerController.startPause(getItem()); mController.startPause();
// Persist value changes
update();
} }
@OnClick(R.id.add_one_minute) @OnClick(R.id.add_one_minute)
void addOneMinute() { void addOneMinute() {
getItem().addOneMinute(); mController.addOneMinute();
// Persist end time increase
update();
} }
@OnClick(R.id.stop) @OnClick(R.id.stop)
void stop() { void stop() {
getItem().stop(); mController.stop();
update();
} }
private void bindLabel(String label) { private void bindLabel(String label) {
@ -113,13 +106,4 @@ public class TimerViewHolder extends BaseViewHolder<Timer> {
mAddOneMinute.setVisibility(visibility); mAddOneMinute.setVisibility(visibility);
mStop.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);
}
} }

View File

@ -33,6 +33,8 @@ public class TimersFragment extends RecyclerViewFragment<
public void onActivityResult(int requestCode, int resultCode, Intent data) { public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode != Activity.RESULT_OK || data == null) if (resultCode != Activity.RESULT_OK || data == null)
return; 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 hour = data.getIntExtra(EditTimerActivity.EXTRA_HOUR, -1);
int minute = data.getIntExtra(EditTimerActivity.EXTRA_MINUTE, -1); int minute = data.getIntExtra(EditTimerActivity.EXTRA_MINUTE, -1);
int second = data.getIntExtra(EditTimerActivity.EXTRA_SECOND, -1); int second = data.getIntExtra(EditTimerActivity.EXTRA_SECOND, -1);

View File

@ -1,22 +1,29 @@
package com.philliphsu.clock2.timers; package com.philliphsu.clock2.timers;
import android.content.Intent;
import android.os.Bundle; 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 @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
stopService(new Intent(this, TimerNotificationService.class));
TimerNotificationService.cancelNotification(this, getRingingObject().getId());
} }
// @Override @Override
// public Loader<Timer> onCreateLoader(long itemId) { public int layoutResource() {
// return new TimerLoader(this, itemId); return R.layout.activity_ringtone;
// } }
// @Override @Override
// public int layoutResource() { protected Class<? extends RingtoneService> getRingtoneServiceClass() {
// return R.layout.activity_ringtone; return TimerRingtoneService.class;
// } }
} }

View File

@ -34,6 +34,7 @@ public final class AlarmController {
private final Context mAppContext; private final Context mAppContext;
private final View mSnackbarAnchor; private final View mSnackbarAnchor;
// TODO: Why aren't we using AsyncAlarmsTableUpdateHandler?
private final AlarmsTableManager mTableManager; private final AlarmsTableManager mTableManager;
/** /**
@ -184,7 +185,7 @@ public final class AlarmController {
private PendingIntent alarmIntent(Alarm alarm, boolean retrievePrevious) { private PendingIntent alarmIntent(Alarm alarm, boolean retrievePrevious) {
// TODO: Use appropriate subclass instead // TODO: Use appropriate subclass instead
Intent intent = new Intent(mAppContext, AlarmActivity.class) Intent intent = new Intent(mAppContext, AlarmActivity.class)
.putExtra(AlarmActivity.EXTRA_ITEM, alarm); .putExtra(AlarmActivity.EXTRA_RINGING_OBJECT, alarm);
int flag = retrievePrevious ? FLAG_NO_CREATE : FLAG_CANCEL_CURRENT; int flag = retrievePrevious ? FLAG_NO_CREATE : FLAG_CANCEL_CURRENT;
PendingIntent pi = getActivity(mAppContext, alarm.intId(), intent, flag); PendingIntent pi = getActivity(mAppContext, alarm.intId(), intent, flag);
// Even when we try to retrieve a previous instance that actually did exist, // Even when we try to retrieve a previous instance that actually did exist,

View File

@ -181,7 +181,7 @@ public final class AlarmUtils {
private static PendingIntent alarmIntent(Context context, Alarm alarm, boolean retrievePrevious) { private static PendingIntent alarmIntent(Context context, Alarm alarm, boolean retrievePrevious) {
// TODO: Use appropriate subclass instead // TODO: Use appropriate subclass instead
Intent intent = new Intent(context, AlarmActivity.class) Intent intent = new Intent(context, AlarmActivity.class)
.putExtra(AlarmActivity.EXTRA_ITEM, alarm); .putExtra(AlarmActivity.EXTRA_RINGING_OBJECT, alarm);
int flag = retrievePrevious ? FLAG_NO_CREATE : FLAG_CANCEL_CURRENT; int flag = retrievePrevious ? FLAG_NO_CREATE : FLAG_CANCEL_CURRENT;
PendingIntent pi = getActivity(context, alarm.intId(), intent, flag); PendingIntent pi = getActivity(context, alarm.intId(), intent, flag);
// Even when we try to retrieve a previous instance that actually did exist, // Even when we try to retrieve a previous instance that actually did exist,

View File

@ -193,4 +193,7 @@
<string name="title_activity_create_timer">CreateTimerActivity</string> <string name="title_activity_create_timer">CreateTimerActivity</string>
<string name="timer">Timer</string> <string name="timer">Timer</string>
<string name="times_up">Time\'s up</string>
<string name="add_one_minute">Add 1 minute</string>
<string name="stop">Stop</string>
</resources> </resources>