Implement ticking countdown text for timer notification
This commit is contained in:
parent
f3aa9e8ed4
commit
df19d6ec4b
@ -47,7 +47,12 @@ public final class AsyncTimersTableUpdateHandler extends AsyncDatabaseTableUpdat
|
|||||||
// will remove and replace it.
|
// will remove and replace it.
|
||||||
scheduleAlarm(timer);
|
scheduleAlarm(timer);
|
||||||
} else {
|
} else {
|
||||||
cancelAlarm(timer, !timer.hasStarted());
|
boolean removeNotification = !timer.hasStarted();
|
||||||
|
cancelAlarm(timer, removeNotification);
|
||||||
|
if (!removeNotification) {
|
||||||
|
// Post a new notification to reflect the paused state of the timer
|
||||||
|
TimerNotificationService.showNotification(getContext(), timer);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -21,15 +21,10 @@ import android.content.Context;
|
|||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.Message;
|
import android.os.Message;
|
||||||
import android.os.SystemClock;
|
import android.os.SystemClock;
|
||||||
import android.text.format.DateUtils;
|
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import java.util.Formatter;
|
|
||||||
import java.util.IllegalFormatException;
|
|
||||||
import java.util.Locale;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by Phillip Hsu on 7/25/2016.
|
* Created by Phillip Hsu on 7/25/2016.
|
||||||
*
|
*
|
||||||
@ -52,19 +47,11 @@ public class CountdownChronometer extends TextView {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private long mBase;
|
|
||||||
private long mNow; // the currently displayed time
|
|
||||||
private boolean mVisible;
|
private boolean mVisible;
|
||||||
private boolean mStarted;
|
private boolean mStarted;
|
||||||
private boolean mRunning;
|
private boolean mRunning;
|
||||||
private boolean mLogged;
|
private final CountdownDelegate mDelegate = new CountdownDelegate();
|
||||||
private String mFormat;
|
|
||||||
private Formatter mFormatter;
|
|
||||||
private Locale mFormatterLocale;
|
|
||||||
private Object[] mFormatterArgs = new Object[1];
|
|
||||||
private StringBuilder mFormatBuilder;
|
|
||||||
private OnChronometerTickListener mOnChronometerTickListener;
|
private OnChronometerTickListener mOnChronometerTickListener;
|
||||||
private StringBuilder mRecycle = new StringBuilder(8);
|
|
||||||
|
|
||||||
private static final int TICK_WHAT = 2;
|
private static final int TICK_WHAT = 2;
|
||||||
|
|
||||||
@ -112,8 +99,8 @@ public class CountdownChronometer extends TextView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void init() {
|
private void init() {
|
||||||
mBase = SystemClock.elapsedRealtime();
|
mDelegate.init();
|
||||||
updateText(mBase);
|
updateText(SystemClock.elapsedRealtime());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -123,7 +110,7 @@ public class CountdownChronometer extends TextView {
|
|||||||
*/
|
*/
|
||||||
// @android.view.RemotableViewMethod
|
// @android.view.RemotableViewMethod
|
||||||
public void setBase(long base) {
|
public void setBase(long base) {
|
||||||
mBase = base;
|
mDelegate.setBase(base);
|
||||||
dispatchChronometerTick();
|
dispatchChronometerTick();
|
||||||
updateText(SystemClock.elapsedRealtime());
|
updateText(SystemClock.elapsedRealtime());
|
||||||
}
|
}
|
||||||
@ -132,7 +119,7 @@ public class CountdownChronometer extends TextView {
|
|||||||
* Return the base time as set through {@link #setBase}.
|
* Return the base time as set through {@link #setBase}.
|
||||||
*/
|
*/
|
||||||
public long getBase() {
|
public long getBase() {
|
||||||
return mBase;
|
return mDelegate.getBase();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -155,17 +142,14 @@ public class CountdownChronometer extends TextView {
|
|||||||
*/
|
*/
|
||||||
// @android.view.RemotableViewMethod
|
// @android.view.RemotableViewMethod
|
||||||
public void setFormat(String format) {
|
public void setFormat(String format) {
|
||||||
mFormat = format;
|
mDelegate.setFormat(format);
|
||||||
if (format != null && mFormatBuilder == null) {
|
|
||||||
mFormatBuilder = new StringBuilder(format.length() * 2);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the current format string as set through {@link #setFormat}.
|
* Returns the current format string as set through {@link #setFormat}.
|
||||||
*/
|
*/
|
||||||
public String getFormat() {
|
public String getFormat() {
|
||||||
return mFormat;
|
return mDelegate.getFormat();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -236,30 +220,7 @@ public class CountdownChronometer extends TextView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private synchronized void updateText(long now) {
|
private synchronized void updateText(long now) {
|
||||||
mNow = now;
|
setText(mDelegate.formatElapsedTime(now));
|
||||||
long seconds = mBase - now;
|
|
||||||
seconds /= 1000;
|
|
||||||
String text = DateUtils.formatElapsedTime(mRecycle, seconds);
|
|
||||||
|
|
||||||
if (mFormat != null) {
|
|
||||||
Locale loc = Locale.getDefault();
|
|
||||||
if (mFormatter == null || !loc.equals(mFormatterLocale)) {
|
|
||||||
mFormatterLocale = loc;
|
|
||||||
mFormatter = new Formatter(mFormatBuilder, loc);
|
|
||||||
}
|
|
||||||
mFormatBuilder.setLength(0);
|
|
||||||
mFormatterArgs[0] = text;
|
|
||||||
try {
|
|
||||||
mFormatter.format(mFormat, mFormatterArgs);
|
|
||||||
text = mFormatBuilder.toString();
|
|
||||||
} catch (IllegalFormatException ex) {
|
|
||||||
if (!mLogged) {
|
|
||||||
Log.w(TAG, "Illegal format string: " + mFormat);
|
|
||||||
mLogged = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
setText(text);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateRunning() {
|
private void updateRunning() {
|
||||||
|
|||||||
@ -0,0 +1,81 @@
|
|||||||
|
package com.philliphsu.clock2.timers;
|
||||||
|
|
||||||
|
import android.os.SystemClock;
|
||||||
|
import android.text.format.DateUtils;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import java.util.Formatter;
|
||||||
|
import java.util.IllegalFormatException;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Phillip Hsu on 9/7/2016.
|
||||||
|
*
|
||||||
|
* A helper class for CountdownChronometer that handles formatting the countdown text.
|
||||||
|
* TODO: A similar delegate class can also be made for ChronometerWithMillis. However, try to
|
||||||
|
* use a common base class between this and ChronometerWithMillis.
|
||||||
|
*/
|
||||||
|
final class CountdownDelegate {
|
||||||
|
private static final String TAG = "CountdownDelegate";
|
||||||
|
|
||||||
|
private long mBase;
|
||||||
|
private long mNow; // the currently displayed time
|
||||||
|
private boolean mLogged;
|
||||||
|
private String mFormat;
|
||||||
|
private Formatter mFormatter;
|
||||||
|
private Locale mFormatterLocale;
|
||||||
|
private Object[] mFormatterArgs = new Object[1];
|
||||||
|
private StringBuilder mFormatBuilder;
|
||||||
|
private StringBuilder mRecycle = new StringBuilder(8);
|
||||||
|
|
||||||
|
void init() {
|
||||||
|
mBase = SystemClock.elapsedRealtime();
|
||||||
|
}
|
||||||
|
|
||||||
|
void setBase(long base) {
|
||||||
|
mBase = base;
|
||||||
|
}
|
||||||
|
|
||||||
|
long getBase() {
|
||||||
|
return mBase;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setFormat(String format) {
|
||||||
|
mFormat = format;
|
||||||
|
if (format != null && mFormatBuilder == null) {
|
||||||
|
mFormatBuilder = new StringBuilder(format.length() * 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String getFormat() {
|
||||||
|
return mFormat;
|
||||||
|
}
|
||||||
|
|
||||||
|
String formatElapsedTime(long now) {
|
||||||
|
mNow = now;
|
||||||
|
long seconds = mBase - now;
|
||||||
|
seconds /= 1000;
|
||||||
|
String text = DateUtils.formatElapsedTime(mRecycle, seconds);
|
||||||
|
|
||||||
|
if (mFormat != null) {
|
||||||
|
Locale loc = Locale.getDefault();
|
||||||
|
if (mFormatter == null || !loc.equals(mFormatterLocale)) {
|
||||||
|
mFormatterLocale = loc;
|
||||||
|
mFormatter = new Formatter(mFormatBuilder, loc);
|
||||||
|
}
|
||||||
|
mFormatBuilder.setLength(0);
|
||||||
|
mFormatterArgs[0] = text;
|
||||||
|
try {
|
||||||
|
mFormatter.format(mFormat, mFormatterArgs);
|
||||||
|
text = mFormatBuilder.toString();
|
||||||
|
} catch (IllegalFormatException ex) {
|
||||||
|
if (!mLogged) {
|
||||||
|
Log.w(TAG, "Illegal format string: " + mFormat);
|
||||||
|
mLogged = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,17 +1,22 @@
|
|||||||
package com.philliphsu.clock2.timers;
|
package com.philliphsu.clock2.timers;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
import android.app.NotificationManager;
|
import android.app.NotificationManager;
|
||||||
import android.app.PendingIntent;
|
import android.app.PendingIntent;
|
||||||
import android.app.Service;
|
import android.app.Service;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.HandlerThread;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
|
import android.os.Message;
|
||||||
|
import android.os.SystemClock;
|
||||||
import android.support.annotation.DrawableRes;
|
import android.support.annotation.DrawableRes;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import android.support.v4.app.NotificationCompat;
|
import android.support.v4.app.NotificationCompat;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
import com.philliphsu.clock2.AsyncTimersTableUpdateHandler;
|
import com.philliphsu.clock2.AsyncTimersTableUpdateHandler;
|
||||||
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;
|
||||||
|
|
||||||
@ -23,7 +28,7 @@ import com.philliphsu.clock2.Timer;
|
|||||||
* our instance state.
|
* our instance state.
|
||||||
*/
|
*/
|
||||||
public class TimerNotificationService extends Service {
|
public class TimerNotificationService extends Service {
|
||||||
private static final String TAG = "TimerNotificationService";
|
private static final String TAG = "TimerNotifService";
|
||||||
|
|
||||||
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";
|
||||||
@ -33,6 +38,10 @@ public class TimerNotificationService extends Service {
|
|||||||
|
|
||||||
private Timer mTimer;
|
private Timer mTimer;
|
||||||
private TimerController mController;
|
private TimerController mController;
|
||||||
|
private NotificationCompat.Builder mNoteBuilder;
|
||||||
|
private NotificationManager mNotificationManager;
|
||||||
|
private final CountdownDelegate mCountdownDelegate = new CountdownDelegate();
|
||||||
|
private MyHandlerThread mThread;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper method to start this Service for its default action: to show
|
* Helper method to start this Service for its default action: to show
|
||||||
@ -52,10 +61,46 @@ public class TimerNotificationService extends Service {
|
|||||||
* @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
|
||||||
*/
|
*/
|
||||||
public static void cancelNotification(Context context, long timerId) {
|
public static void cancelNotification(Context context, long timerId) { // TODO: remove long param
|
||||||
NotificationManager nm = (NotificationManager)
|
// NotificationManager nm = (NotificationManager)
|
||||||
context.getSystemService(Context.NOTIFICATION_SERVICE);
|
// context.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||||
nm.cancel(TAG, (int) timerId);
|
// nm.cancel(TAG, (int) timerId);
|
||||||
|
context.stopService(new Intent(context, TimerNotificationService.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate() {
|
||||||
|
super.onCreate();
|
||||||
|
mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
|
||||||
|
|
||||||
|
// Create base note
|
||||||
|
mNoteBuilder = new NotificationCompat.Builder(this)
|
||||||
|
.setSmallIcon(R.drawable.ic_timer_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*/, mTimer.getId());
|
||||||
|
// mNoteBuilder.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..*/));
|
||||||
|
|
||||||
|
mCountdownDelegate.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroy() {
|
||||||
|
super.onDestroy();
|
||||||
|
mNotificationManager.cancelAll();
|
||||||
|
quitThread();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -67,14 +112,26 @@ public class TimerNotificationService extends Service {
|
|||||||
throw new IllegalStateException("Cannot start TimerNotificationService without a Timer");
|
throw new IllegalStateException("Cannot start TimerNotificationService without a Timer");
|
||||||
}
|
}
|
||||||
mController = new TimerController(mTimer, new AsyncTimersTableUpdateHandler(this, null));
|
mController = new TimerController(mTimer, new AsyncTimersTableUpdateHandler(this, null));
|
||||||
// TODO: Spawn your own thread to update the countdown text
|
// The note's title should change here every time,
|
||||||
showNotification();
|
// especially if the Timer's label was updated.
|
||||||
|
String title = mTimer.label();
|
||||||
|
if (title.isEmpty()) {
|
||||||
|
title = getString(R.string.timer);
|
||||||
|
}
|
||||||
|
mNoteBuilder.setContentTitle(title);
|
||||||
|
syncNotificationWithTimerState(mTimer.isRunning());
|
||||||
} else if (ACTION_ADD_ONE_MINUTE.equals(action)) {
|
} else if (ACTION_ADD_ONE_MINUTE.equals(action)) {
|
||||||
|
// While the notification's countdown would automatically be extended by one minute,
|
||||||
|
// there is a noticeable delay before the minute gets added on.
|
||||||
|
// Update the text immediately, because there's no harm in doing so.
|
||||||
|
mCountdownDelegate.setBase(mCountdownDelegate.getBase() + 60000);
|
||||||
|
// Dispatch a one-time (non-looping) message so as not to conflate
|
||||||
|
// with the current set of looping messages.
|
||||||
|
mThread.sendMessage(MSG_DISPATCH_TICK);
|
||||||
mController.addOneMinute();
|
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)) {
|
||||||
mController.startPause();
|
mController.startPause();
|
||||||
showNotification(); // Update the notification
|
syncNotificationWithTimerState(mTimer.isRunning());
|
||||||
} else if (ACTION_STOP.equals(action)) {
|
} else if (ACTION_STOP.equals(action)) {
|
||||||
mController.stop();
|
mController.stop();
|
||||||
stopSelf();
|
stopSelf();
|
||||||
@ -91,57 +148,120 @@ public class TimerNotificationService extends Service {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showNotification() {
|
private void syncNotificationWithTimerState(boolean running) {
|
||||||
// Base note
|
// The actions from the last time we configured the Builder are still here.
|
||||||
NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
|
// We have to retain the relative ordering of the actions while updating
|
||||||
.setSmallIcon(R.drawable.ic_timer_24dp)
|
// just the start/pause action, so clear them and set them again.
|
||||||
.setShowWhen(false)
|
// TODO: The source indicates mActions is hidden, so how are we able to access it?
|
||||||
.setOngoing(true);
|
// Will it remain accessible for all SDK versions? If not, we would have to rebuild
|
||||||
// TODO: Set content intent so that when clicked, we launch
|
// the entire notification with a new local Builder instance.
|
||||||
// TimersFragment and scroll to the given timer id. The following
|
mNoteBuilder.mActions.clear();
|
||||||
// is merely pseudocode.
|
addAction(ACTION_ADD_ONE_MINUTE, R.drawable.ic_add_24dp, getString(R.string.minute));
|
||||||
Intent contentIntent = new Intent(this, MainActivity.class);
|
addAction(ACTION_START_PAUSE,
|
||||||
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..
|
|
||||||
contentIntent,
|
|
||||||
0/*Shouldn't need a flag..*/));
|
|
||||||
// TODO: Use a handler to continually update the countdown text
|
|
||||||
|
|
||||||
String title = mTimer.label();
|
|
||||||
if (title.isEmpty()) {
|
|
||||||
title = getString(R.string.timer);
|
|
||||||
}
|
|
||||||
builder.setContentTitle(title);
|
|
||||||
|
|
||||||
addAction(builder, ACTION_ADD_ONE_MINUTE, R.drawable.ic_add_24dp, getString(R.string.minute));
|
|
||||||
|
|
||||||
boolean running = mTimer.isRunning();
|
|
||||||
addAction(builder, ACTION_START_PAUSE,
|
|
||||||
running ? R.drawable.ic_pause_24dp : R.drawable.ic_start_24dp,
|
running ? R.drawable.ic_pause_24dp : R.drawable.ic_start_24dp,
|
||||||
getString(running ? R.string.pause : R.string.resume));
|
getString(running ? R.string.pause : R.string.resume));
|
||||||
addAction(builder, ACTION_STOP, R.drawable.ic_stop_24dp, getString(R.string.stop));
|
addAction(ACTION_STOP, R.drawable.ic_stop_24dp, getString(R.string.stop));
|
||||||
|
|
||||||
NotificationManager nm = (NotificationManager)
|
// Post the notification immediately, as the HandlerThread will delay its first
|
||||||
getSystemService(Context.NOTIFICATION_SERVICE);
|
// message delivery.
|
||||||
nm.notify(TAG, mTimer.getIntId(), builder.build());
|
updateNotification();
|
||||||
|
// Quit any previously executed thread. If running == false, the effect is obvious;
|
||||||
|
// otherwise, we're preparing for the start of a new thread.
|
||||||
|
quitThread();
|
||||||
|
|
||||||
|
if (running) {
|
||||||
|
// An instance of Thread cannot be started more than once. You must create
|
||||||
|
// a new instance if you want to start the Thread's work again.
|
||||||
|
mThread = new MyHandlerThread();
|
||||||
|
// Initializes this thread as a looper. HandlerThread.run() will be executed
|
||||||
|
// in this thread.
|
||||||
|
// This gives you a chance to create handlers that then reference this looper,
|
||||||
|
// before actually starting the loop.
|
||||||
|
mThread.start();
|
||||||
|
// If this thread has been started, this method will block *the calling thread*
|
||||||
|
// until the looper has been initialized. This ensures the handler thread is
|
||||||
|
// fully initialized before we proceed.
|
||||||
|
mThread.getLooper();
|
||||||
|
Log.d(TAG, "Looper initialized");
|
||||||
|
mCountdownDelegate.setBase(SystemClock.elapsedRealtime() + mTimer.timeRemaining());
|
||||||
|
mThread.sendMessage(MSG_WHAT);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Builds and adds the specified action to the notification's builder.
|
* Builds and adds the specified action to the notification's mNoteBuilder.
|
||||||
*/
|
*/
|
||||||
private void addAction(NotificationCompat.Builder noteBuilder, String action,
|
private void addAction(String action, @DrawableRes int icon, String actionTitle) {
|
||||||
@DrawableRes int icon, String actionTitle) {
|
|
||||||
Intent intent = new Intent(this, TimerNotificationService.class)
|
Intent intent = new Intent(this, TimerNotificationService.class)
|
||||||
.setAction(action);
|
.setAction(action);
|
||||||
// .putExtra(EXTRA_TIMER, mTimer);
|
// .putExtra(EXTRA_TIMER, mTimer);
|
||||||
PendingIntent pi = PendingIntent.getService(this,
|
PendingIntent pi = PendingIntent.getService(this,
|
||||||
mTimer.getIntId(), intent, 0/*no flags*/);
|
mTimer.getIntId(), intent, 0/*no flags*/);
|
||||||
noteBuilder.addAction(icon, actionTitle, pi);
|
mNoteBuilder.addAction(icon, actionTitle, pi);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Causes the handler thread's looper to terminate without processing
|
||||||
|
* any more messages in the message queue.
|
||||||
|
*/
|
||||||
|
private void quitThread() {
|
||||||
|
if (mThread != null && mThread.isAlive()) {
|
||||||
|
mThread.quit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateNotification() {
|
||||||
|
String text = mCountdownDelegate.formatElapsedTime(SystemClock.elapsedRealtime());
|
||||||
|
mNoteBuilder.setContentText(text);
|
||||||
|
mNotificationManager.notify(TAG, mTimer.getIntId(), mNoteBuilder.build());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final int MSG_WHAT = 2;
|
||||||
|
private static final int MSG_DISPATCH_TICK = 3;
|
||||||
|
|
||||||
|
private class MyHandlerThread extends HandlerThread {
|
||||||
|
private Handler mHandler;
|
||||||
|
|
||||||
|
public MyHandlerThread() {
|
||||||
|
super("MyHandlerThread");
|
||||||
|
}
|
||||||
|
|
||||||
|
// There won't be a memory leak since our handler is using a looper that is not
|
||||||
|
// associated with the main thread. The full Lint warning confirmed this.
|
||||||
|
@SuppressLint("HandlerLeak")
|
||||||
|
@Override
|
||||||
|
protected void onLooperPrepared() {
|
||||||
|
Log.d(TAG, "Looper fully prepared");
|
||||||
|
// This is called after the looper has completed initializing, but before
|
||||||
|
// it starts looping through its message queue. Right now, there is no
|
||||||
|
// message queue, so this is the place to create it.
|
||||||
|
// By default, the constructor associates this handler with this thread's looper.
|
||||||
|
mHandler = new Handler() {
|
||||||
|
@Override
|
||||||
|
public void handleMessage(Message m) {
|
||||||
|
updateNotification();
|
||||||
|
if (m.what != MSG_DISPATCH_TICK) {
|
||||||
|
sendMessageDelayed(Message.obtain(this, MSG_WHAT), 1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sendMessage(int what) {
|
||||||
|
// We've encountered NPEs because the handler was still
|
||||||
|
// uninitialized even at this point. I assume we cannot rely on any
|
||||||
|
// defined order in which different threads execute their code.
|
||||||
|
// Block the calling thread from proceeding until the handler thread
|
||||||
|
// completes the handler's initialization.
|
||||||
|
while (mHandler == null);
|
||||||
|
|
||||||
|
Log.d(TAG, "Sending message");
|
||||||
|
Message msg = Message.obtain(mHandler, what);
|
||||||
|
if (what == MSG_DISPATCH_TICK) {
|
||||||
|
mHandler.sendMessage(msg);
|
||||||
|
} else if (what == MSG_WHAT) {
|
||||||
|
mHandler.sendMessageDelayed(msg, 1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue
Block a user