Merge "Show chronometer in expired timer notification" into ub-deskclock-fantasy
diff --git a/src/com/android/deskclock/data/StopwatchModel.java b/src/com/android/deskclock/data/StopwatchModel.java
index ecbd864..3589666 100644
--- a/src/com/android/deskclock/data/StopwatchModel.java
+++ b/src/com/android/deskclock/data/StopwatchModel.java
@@ -44,6 +44,7 @@
private final NotificationManagerCompat mNotificationManager;
/** Update stopwatch notification when locale changes. */
+ @SuppressWarnings("FieldCanBeLocal")
private final BroadcastReceiver mLocaleChangedReceiver = new LocaleChangedReceiver();
/** The listeners to notify when the stopwatch or its laps change. */
diff --git a/src/com/android/deskclock/data/TimerModel.java b/src/com/android/deskclock/data/TimerModel.java
index b64ac39..745296b 100644
--- a/src/com/android/deskclock/data/TimerModel.java
+++ b/src/com/android/deskclock/data/TimerModel.java
@@ -31,9 +31,7 @@
import android.media.RingtoneManager;
import android.net.Uri;
import android.support.annotation.StringRes;
-import android.support.v4.app.NotificationCompat;
import android.support.v4.app.NotificationManagerCompat;
-import android.text.TextUtils;
import android.util.ArraySet;
import com.android.deskclock.AlarmAlertWakeLock;
@@ -42,7 +40,6 @@
import com.android.deskclock.Utils;
import com.android.deskclock.events.Events;
import com.android.deskclock.settings.SettingsActivity;
-import com.android.deskclock.timer.ExpiredTimersActivity;
import com.android.deskclock.timer.TimerKlaxon;
import com.android.deskclock.timer.TimerService;
@@ -75,12 +72,14 @@
private final NotificationManagerCompat mNotificationManager;
/** Update timer notification when locale changes. */
+ @SuppressWarnings("FieldCanBeLocal")
private final BroadcastReceiver mLocaleChangedReceiver = new LocaleChangedReceiver();
/**
* Retain a hard reference to the shared preference observer to prevent it from being garbage
* collected. See {@link SharedPreferences#registerOnSharedPreferenceChangeListener} for detail.
*/
+ @SuppressWarnings("FieldCanBeLocal")
private final OnSharedPreferenceChangeListener mPreferenceListener = new PreferenceListener();
/** The listeners to notify when a timer is added, updated or removed. */
@@ -662,68 +661,8 @@
return;
}
- // Generate some descriptive text, a title, and an action name based on the timer count.
- final int timerId;
- final String contentText;
- final String contentTitle;
- final String resetActionTitle;
- if (expired.size() > 1) {
- timerId = -1;
- contentText = mContext.getString(R.string.timer_multi_times_up, expired.size());
- contentTitle = mContext.getString(R.string.timer_notification_label);
- resetActionTitle = mContext.getString(R.string.timer_stop_all);
- } else {
- final Timer timer = expired.get(0);
- timerId = timer.getId();
- resetActionTitle = mContext.getString(R.string.timer_stop);
- contentText = mContext.getString(R.string.timer_times_up);
-
- final String label = timer.getLabel();
- if (TextUtils.isEmpty(label)) {
- contentTitle = mContext.getString(R.string.timer_notification_label);
- } else {
- contentTitle = label;
- }
- }
-
- // Content intent shows the timer full screen when clicked.
- final Intent content = new Intent(mContext, ExpiredTimersActivity.class);
- final PendingIntent pendingContent = Utils.pendingActivityIntent(mContext, content);
-
- // Full screen intent has flags so it is different than the content intent.
- final Intent fullScreen = new Intent(mContext, ExpiredTimersActivity.class)
- .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_NO_USER_ACTION);
- final PendingIntent pendingFullScreen = Utils.pendingActivityIntent(mContext, fullScreen);
-
- // First action intent is either reset single timer or reset all timers.
- final Intent reset = TimerService.createResetExpiredTimersIntent(mContext);
- final PendingIntent pendingReset = Utils.pendingServiceIntent(mContext, reset);
-
- final NotificationCompat.Builder builder = new NotificationCompat.Builder(mContext)
- .setWhen(0)
- .setOngoing(true)
- .setLocalOnly(true)
- .setAutoCancel(false)
- .setContentText(contentText)
- .setContentTitle(contentTitle)
- .setContentIntent(pendingContent)
- .setSmallIcon(R.drawable.stat_notify_timer)
- .setFullScreenIntent(pendingFullScreen, true)
- .setPriority(NotificationCompat.PRIORITY_MAX)
- .setDefaults(NotificationCompat.DEFAULT_LIGHTS)
- .addAction(R.drawable.ic_stop_24dp, resetActionTitle, pendingReset);
-
- // Add a second action if only a single timer is expired.
- if (expired.size() == 1) {
- // Second action intent adds a minute to a single timer.
- final Intent addMinute = TimerService.createAddMinuteTimerIntent(mContext, timerId);
- final PendingIntent pendingAddMinute = Utils.pendingServiceIntent(mContext, addMinute);
- final String addMinuteTitle = mContext.getString(R.string.timer_plus_1_min);
- builder.addAction(R.drawable.ic_add_24dp, addMinuteTitle, pendingAddMinute);
- }
-
- // Update the notification.
- final Notification notification = builder.build();
+ // Otherwise build and post a foreground notification reflecting the latest expired timers.
+ final Notification notification = getNotificationBuilder().buildHeadsUp(mContext, expired);
final int notificationId = mNotificationModel.getExpiredTimerNotificationId();
mService.startForeground(notificationId, notification);
}
@@ -786,5 +725,12 @@
* @return a notification reporting the state of the {@code unexpiredTimers}
*/
Notification build(Context context, List<Timer> unexpiredTimers);
+
+ /**
+ * @param context a context to use for fetching resources
+ * @param expiredTimers all expired timers
+ * @return a heads-up notification reporting the state of the {@code expiredTimers}
+ */
+ Notification buildHeadsUp(Context context, List<Timer> expiredTimers);
}
}
\ No newline at end of file
diff --git a/src/com/android/deskclock/data/TimerNotificationBuilderN.java b/src/com/android/deskclock/data/TimerNotificationBuilderN.java
index b8b52d9..50a7e08 100644
--- a/src/com/android/deskclock/data/TimerNotificationBuilderN.java
+++ b/src/com/android/deskclock/data/TimerNotificationBuilderN.java
@@ -31,6 +31,7 @@
import com.android.deskclock.HandleDeskClockApiCalls;
import com.android.deskclock.R;
import com.android.deskclock.Utils;
+import com.android.deskclock.timer.ExpiredTimersActivity;
import com.android.deskclock.timer.TimerService;
import java.util.ArrayList;
@@ -51,29 +52,24 @@
// Compute some values required below.
final boolean running = timer.isRunning();
- final String pname = context.getPackageName();
final Resources res = context.getResources();
- // The in-app timer display rounds *up* to the next second. To mirror that behavior in the
- // notification's Chronometer, pad in an extra second.
- final long remainingTime = timer.getRemainingTime() + SECOND_IN_MILLIS;
-
- // Chronometer will reach 0:00 at remainingTime milliseconds in the future.
- final long base = SystemClock.elapsedRealtime() + remainingTime;
-
+ final long base = getChronometerBase(timer);
+ final String pname = context.getPackageName();
final RemoteViews content = new RemoteViews(pname, R.layout.chronometer_notif_content);
content.setChronometerCountDown(R.id.chronometer, true);
content.setChronometer(R.id.chronometer, base, null, running);
final List<Notification.Action> actions = new ArrayList<>(2);
+ final CharSequence stateText;
if (count == 1) {
if (running) {
// Single timer is running.
if (TextUtils.isEmpty(timer.getLabel())) {
- content.setTextViewText(R.id.state, res.getString(R.string.timer_notification_label));
+ stateText = res.getString(R.string.timer_notification_label);
} else {
- content.setTextViewText(R.id.state, timer.getLabel());
+ stateText = timer.getLabel();
}
// Left button: Pause
@@ -98,7 +94,7 @@
} else {
// Single timer is paused.
- content.setTextViewText(R.id.state, res.getString(R.string.timer_paused));
+ stateText = res.getString(R.string.timer_paused);
// Left button: Start
final Intent start = new Intent(context, TimerService.class)
@@ -123,10 +119,10 @@
} else {
if (running) {
// At least one timer is running.
- content.setTextViewText(R.id.state, res.getString(R.string.timers_in_use, count));
+ stateText = res.getString(R.string.timers_in_use, count);
} else {
// All timers are paused.
- content.setTextViewText(R.id.state, res.getString(R.string.timers_stopped, count));
+ stateText = res.getString(R.string.timers_stopped, count);
}
final Intent reset = TimerService.createResetUnexpiredTimersIntent(context);
@@ -137,6 +133,8 @@
actions.add(new Notification.Action.Builder(icon1, title1, intent1).build());
}
+ content.setTextViewText(R.id.state, stateText);
+
// Intent to load the app and show the timer when the notification is tapped.
final Intent showApp = new Intent(context, HandleDeskClockApiCalls.class)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
@@ -162,4 +160,91 @@
.setActions(actions.toArray(new Notification.Action[actions.size()]))
.build();
}
+
+ @Override
+ public Notification buildHeadsUp(Context context, List<Timer> expired) {
+ final Timer timer = expired.get(0);
+
+ // First action intent is to reset all timers.
+ final Icon icon1 = Icon.createWithResource(context, R.drawable.ic_stop_24dp);
+ final Intent reset = TimerService.createResetExpiredTimersIntent(context);
+ final PendingIntent intent1 = Utils.pendingServiceIntent(context, reset);
+
+ // Generate some descriptive text, a title, and an action name based on the timer count.
+ final CharSequence stateText;
+ final int count = expired.size();
+ final List<Notification.Action> actions = new ArrayList<>(2);
+ if (count == 1) {
+ final String label = timer.getLabel();
+ if (TextUtils.isEmpty(label)) {
+ stateText = context.getString(R.string.timer_times_up);
+ } else {
+ stateText = label;
+ }
+
+ // Left button: Reset single timer
+ final CharSequence title1 = context.getString(R.string.timer_stop);
+ actions.add(new Notification.Action.Builder(icon1, title1, intent1).build());
+
+ // Right button: Add minute
+ final Intent addTime = TimerService.createAddMinuteTimerIntent(context, timer.getId());
+ final PendingIntent intent2 = Utils.pendingServiceIntent(context, addTime);
+ final Icon icon2 = Icon.createWithResource(context, R.drawable.ic_add_24dp);
+ final CharSequence title2 = context.getString(R.string.timer_plus_1_min);
+ actions.add(new Notification.Action.Builder(icon2, title2, intent2).build());
+
+ } else {
+ stateText = context.getString(R.string.timer_multi_times_up, count);
+
+ // Left button: Reset all timers
+ final CharSequence title1 = context.getString(R.string.timer_stop_all);
+ actions.add(new Notification.Action.Builder(icon1, title1, intent1).build());
+ }
+
+ final long base = getChronometerBase(timer);
+
+ final String pname = context.getPackageName();
+ final RemoteViews contentView = new RemoteViews(pname, R.layout.chronometer_notif_content);
+ contentView.setChronometerCountDown(R.id.chronometer, true);
+ contentView.setChronometer(R.id.chronometer, base, null, true);
+ contentView.setTextViewText(R.id.state, stateText);
+
+ // Content intent shows the timer full screen when clicked.
+ final Intent content = new Intent(context, ExpiredTimersActivity.class);
+ final PendingIntent contentIntent = Utils.pendingActivityIntent(context, content);
+
+ // Full screen intent has flags so it is different than the content intent.
+ final Intent fullScreen = new Intent(context, ExpiredTimersActivity.class)
+ .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_NO_USER_ACTION);
+ final PendingIntent pendingFullScreen = Utils.pendingActivityIntent(context, fullScreen);
+
+ return new Notification.Builder(context)
+ .setOngoing(true)
+ .setLocalOnly(true)
+ .setShowWhen(false)
+ .setAutoCancel(false)
+ .setContentIntent(contentIntent)
+ .setCustomContentView(contentView)
+ .setPriority(Notification.PRIORITY_MAX)
+ .setDefaults(Notification.DEFAULT_LIGHTS)
+ .setSmallIcon(R.drawable.stat_notify_timer)
+ .setFullScreenIntent(pendingFullScreen, true)
+ .setStyle(new Notification.DecoratedCustomViewStyle())
+ .setActions(actions.toArray(new Notification.Action[actions.size()]))
+ .build();
+ }
+
+ /**
+ * @param timer the timer on which to base the chronometer display
+ * @return the time at which the chronometer will/did reach 0:00 in realtime
+ */
+ private static long getChronometerBase(Timer timer) {
+ // The in-app timer display rounds *up* to the next second for positive timer values. Mirror
+ // that behavior in the notification's Chronometer by padding in an extra second as needed.
+ final long remaining = timer.getRemainingTime();
+ final long adjustedRemaining = remaining < 0 ? remaining : remaining + SECOND_IN_MILLIS;
+
+ // Chronometer will/did reach 0:00 adjustedRemaining milliseconds from now.
+ return SystemClock.elapsedRealtime() + adjustedRemaining;
+ }
}
\ No newline at end of file
diff --git a/src/com/android/deskclock/data/TimerNotificationBuilderPreN.java b/src/com/android/deskclock/data/TimerNotificationBuilderPreN.java
index 82ddfec..c645d99 100644
--- a/src/com/android/deskclock/data/TimerNotificationBuilderPreN.java
+++ b/src/com/android/deskclock/data/TimerNotificationBuilderPreN.java
@@ -31,6 +31,7 @@
import com.android.deskclock.HandleDeskClockApiCalls;
import com.android.deskclock.R;
import com.android.deskclock.Utils;
+import com.android.deskclock.timer.ExpiredTimersActivity;
import com.android.deskclock.timer.TimerService;
import java.util.List;
@@ -166,6 +167,73 @@
return builder.build();
}
+ @Override
+ public Notification buildHeadsUp(Context context, List<Timer> expired) {
+ // Generate some descriptive text, a title, and an action name based on the timer count.
+ final int timerId;
+ final String contentText;
+ final String contentTitle;
+ final String resetActionTitle;
+ final int count = expired.size();
+
+ if (count == 1) {
+ final Timer timer = expired.get(0);
+ timerId = timer.getId();
+ resetActionTitle = context.getString(R.string.timer_stop);
+ contentText = context.getString(R.string.timer_times_up);
+
+ final String label = timer.getLabel();
+ if (TextUtils.isEmpty(label)) {
+ contentTitle = context.getString(R.string.timer_notification_label);
+ } else {
+ contentTitle = label;
+ }
+ } else {
+ timerId = -1;
+ contentText = context.getString(R.string.timer_multi_times_up, count);
+ contentTitle = context.getString(R.string.timer_notification_label);
+ resetActionTitle = context.getString(R.string.timer_stop_all);
+ }
+
+ // Content intent shows the expired timers full screen when clicked.
+ final Intent content = new Intent(context, ExpiredTimersActivity.class);
+ final PendingIntent pendingContent = Utils.pendingActivityIntent(context, content);
+
+ // Full screen intent has flags so it is different than the content intent.
+ final Intent fullScreen = new Intent(context, ExpiredTimersActivity.class)
+ .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_NO_USER_ACTION);
+ final PendingIntent pendingFullScreen = Utils.pendingActivityIntent(context, fullScreen);
+
+ // Left button: Reset timer / Reset all timers
+ final Intent reset = TimerService.createResetExpiredTimersIntent(context);
+ final PendingIntent pendingReset = Utils.pendingServiceIntent(context, reset);
+
+ final NotificationCompat.Builder builder = new NotificationCompat.Builder(context)
+ .setOngoing(true)
+ .setLocalOnly(true)
+ .setShowWhen(false)
+ .setAutoCancel(false)
+ .setContentText(contentText)
+ .setContentTitle(contentTitle)
+ .setContentIntent(pendingContent)
+ .setSmallIcon(R.drawable.stat_notify_timer)
+ .setFullScreenIntent(pendingFullScreen, true)
+ .setPriority(NotificationCompat.PRIORITY_MAX)
+ .setDefaults(NotificationCompat.DEFAULT_LIGHTS)
+ .addAction(R.drawable.ic_stop_24dp, resetActionTitle, pendingReset);
+
+ // Add a second action if only a single timer is expired.
+ if (count == 1) {
+ // Right button: Add minute
+ final Intent addMinute = TimerService.createAddMinuteTimerIntent(context, timerId);
+ final PendingIntent pendingAddMinute = Utils.pendingServiceIntent(context, addMinute);
+ final String addMinuteTitle = context.getString(R.string.timer_plus_1_min);
+ builder.addAction(R.drawable.ic_add_24dp, addMinuteTitle, pendingAddMinute);
+ }
+
+ return builder.build();
+ }
+
/**
* Format "7 hours 52 minutes remaining"
*/