package com.ianhanniballake.contractiontimer.notification;
import android.app.AlarmManager;
import android.app.IntentService;
import android.app.Notification;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.preference.PreferenceManager;
import android.provider.BaseColumns;
import android.support.v4.app.NotificationCompat;
import android.support.v4.app.NotificationManagerCompat;
import android.support.v4.app.RemoteInput;
import android.support.v4.content.ContextCompat;
import android.text.TextUtils;
import android.text.format.DateUtils;
import android.util.Log;
import com.ianhanniballake.contractiontimer.BuildConfig;
import com.ianhanniballake.contractiontimer.R;
import com.ianhanniballake.contractiontimer.appwidget.AppWidgetToggleService;
import com.ianhanniballake.contractiontimer.provider.ContractionContract;
import com.ianhanniballake.contractiontimer.ui.MainActivity;
import com.ianhanniballake.contractiontimer.ui.Preferences;
/**
* IntentService which updates the ongoing notification
*/
public class NotificationUpdateService extends IntentService {
private final static String TAG = NotificationUpdateService.class.getSimpleName();
private static final int NOTIFICATION_ID = 0;
public NotificationUpdateService() {
super(TAG);
}
public static void updateNotification(Context context) {
context.startService(new Intent(context, NotificationUpdateService.class));
}
@Override
protected void onHandleIntent(final Intent intent) {
NoteNoDisplayActivity.checkServiceState(this);
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this);
final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
final boolean notificationsEnabled = preferences.getBoolean(Preferences.NOTIFICATION_ENABLE_PREFERENCE_KEY,
getResources().getBoolean(R.bool.pref_notification_enable_default));
if (!notificationsEnabled) {
if (BuildConfig.DEBUG)
Log.d(TAG, "Notifications disabled, cancelling notification");
notificationManager.cancel(NOTIFICATION_ID);
return;
}
final String[] projection = {BaseColumns._ID,
ContractionContract.Contractions.COLUMN_NAME_START_TIME,
ContractionContract.Contractions.COLUMN_NAME_END_TIME,
ContractionContract.Contractions.COLUMN_NAME_NOTE};
final String selection = ContractionContract.Contractions.COLUMN_NAME_START_TIME + ">?";
final long averagesTimeFrame = Long.parseLong(preferences.getString(
Preferences.AVERAGE_TIME_FRAME_PREFERENCE_KEY,
getString(R.string.pref_average_time_frame_default)));
final long timeCutoff = System.currentTimeMillis() - averagesTimeFrame;
final String[] selectionArgs = {Long.toString(timeCutoff)};
final Cursor data = getContentResolver().query(ContractionContract.Contractions.CONTENT_URI, projection,
selection, selectionArgs, null);
if (data == null || !data.moveToFirst()) {
if (BuildConfig.DEBUG)
Log.d(TAG, "No data found, cancelling notification");
notificationManager.cancel(NOTIFICATION_ID);
if (data != null) {
data.close();
}
return;
}
// Set an alarm to update the notification after first start time + average time frame amount of time
// This ensures that if no contraction has started since then (likely, otherwise we would have been called in
// the mean time) we will fail the above check as there will be no contractions within the average time period
// and the notification will be cancelled
final int startTimeColumnIndex = data.getColumnIndex(ContractionContract.Contractions.COLUMN_NAME_START_TIME);
final long lastStartTime = data.getLong(startTimeColumnIndex);
Intent autoCancelIntent = new Intent(this, NotificationUpdateService.class);
PendingIntent autoCancelPendingIntent = PendingIntent.getService(this, 0, autoCancelIntent, 0);
AlarmManager alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE);
alarmManager.cancel(autoCancelPendingIntent);
// We don't need to wake up the device as it doesn't matter if the notification is cancelled until the device
// is woken up
alarmManager.set(AlarmManager.RTC, lastStartTime + averagesTimeFrame, autoCancelPendingIntent);
// Build the notification
if (BuildConfig.DEBUG)
Log.d(TAG, "Building Notification");
NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
NotificationCompat.Builder publicBuilder = new NotificationCompat.Builder(this);
builder.setSmallIcon(R.drawable.ic_notification)
.setColor(ContextCompat.getColor(this, R.color.primary))
.setCategory(NotificationCompat.CATEGORY_ALARM);
publicBuilder.setSmallIcon(R.drawable.ic_notification)
.setColor(ContextCompat.getColor(this, R.color.primary))
.setCategory(NotificationCompat.CATEGORY_ALARM);
Intent contentIntent = new Intent(this, MainActivity.class);
contentIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
Intent.FLAG_ACTIVITY_CLEAR_TASK |
Intent.FLAG_ACTIVITY_TASK_ON_HOME);
contentIntent.putExtra(MainActivity.LAUNCHED_FROM_NOTIFICATION_EXTRA, true);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, contentIntent,
PendingIntent.FLAG_UPDATE_CURRENT);
builder.setContentIntent(pendingIntent);
publicBuilder.setContentIntent(pendingIntent);
NotificationCompat.WearableExtender wearableExtender = new NotificationCompat.WearableExtender();
// Determine whether a contraction is currently ongoing
final int endTimeColumnIndex = data.getColumnIndex(ContractionContract.Contractions.COLUMN_NAME_END_TIME);
final boolean contractionOngoing = data.isNull(endTimeColumnIndex);
Intent startStopIntent = new Intent(this, AppWidgetToggleService.class);
startStopIntent.putExtra(AppWidgetToggleService.WIDGET_NAME_EXTRA, "notification");
PendingIntent startStopPendingIntent = PendingIntent.getService(this, 0, startStopIntent,
PendingIntent.FLAG_UPDATE_CURRENT);
if (contractionOngoing) {
builder.setContentTitle(getString(R.string.notification_timing));
publicBuilder.setContentTitle(getString(R.string.notification_timing));
builder.addAction(R.drawable.ic_notif_action_stop, getString(R.string.appwidget_contraction_stop),
startStopPendingIntent);
wearableExtender.addAction(new NotificationCompat.Action(R.drawable.ic_wear_action_stop,
getString(R.string.appwidget_contraction_stop),
startStopPendingIntent));
} else {
builder.setContentTitle(getString(R.string.app_name));
publicBuilder.setContentTitle(getString(R.string.app_name));
builder.addAction(R.drawable.ic_notif_action_start, getString(R.string.appwidget_contraction_start),
startStopPendingIntent);
wearableExtender.addAction(new NotificationCompat.Action(R.drawable.ic_wear_action_start,
getString(R.string.appwidget_contraction_start),
startStopPendingIntent));
}
// See if there is a note and build a page if it exists
final int noteColumnIndex = data.getColumnIndex(ContractionContract.Contractions.COLUMN_NAME_NOTE);
final String note = data.getString(noteColumnIndex);
boolean hasNote = !TextUtils.isEmpty(note);
// Fill in the 'when', which will be used to show live progress via the chronometer feature
final long when = contractionOngoing ? data.getLong(startTimeColumnIndex) : data.getLong(endTimeColumnIndex);
builder.setWhen(when);
publicBuilder.setWhen(when);
builder.setUsesChronometer(true);
publicBuilder.setUsesChronometer(true);
// Get the average duration and frequency
double averageDuration = 0;
double averageFrequency = 0;
int numDurations = 0;
int numFrequencies = 0;
while (!data.isAfterLast()) {
final long startTime = data.getLong(startTimeColumnIndex);
if (!data.isNull(endTimeColumnIndex)) {
final long endTime = data.getLong(endTimeColumnIndex);
final long curDuration = endTime - startTime;
averageDuration = (curDuration + numDurations * averageDuration) / (numDurations + 1);
numDurations++;
}
if (data.moveToNext()) {
final long prevContractionStartTime = data.getLong(startTimeColumnIndex);
final long curFrequency = startTime - prevContractionStartTime;
averageFrequency = (curFrequency + numFrequencies * averageFrequency) / (numFrequencies + 1);
numFrequencies++;
}
}
final long averageDurationInSeconds = (long) (averageDuration / 1000);
String formattedAverageDuration = DateUtils.formatElapsedTime(averageDurationInSeconds);
final long averageFrequencyInSeconds = (long) (averageFrequency / 1000);
String formattedAverageFrequency = DateUtils.formatElapsedTime(averageFrequencyInSeconds);
String contentText = getString(R.string.notification_content_text,
formattedAverageDuration, formattedAverageFrequency);
String bigText;
String bigTextWithoutNote = getString(R.string.notification_big_text, formattedAverageDuration,
formattedAverageFrequency);
if (hasNote) {
bigText = getString(R.string.notification_big_text_with_note, formattedAverageDuration,
formattedAverageFrequency, note);
} else {
bigText = bigTextWithoutNote;
}
builder.setContentText(contentText)
.setStyle(new NotificationCompat.BigTextStyle().bigText(bigText));
publicBuilder.setContentText(contentText)
.setStyle(new NotificationCompat.BigTextStyle().bigText(bigTextWithoutNote));
// Close the cursor
data.close();
// Create a separate page for the averages as the big text is not shown on Android Wear in chronometer mode
Notification averagePage = new NotificationCompat.Builder(this)
.setContentTitle(getString(R.string.notification_second_page_title))
.setStyle(new NotificationCompat.InboxStyle()
.setBigContentTitle(getString(R.string.notification_second_page_title))
.addLine(getString(R.string.notification_second_page_duration, formattedAverageDuration))
.addLine(getString(R.string.notification_second_page_frequency, formattedAverageFrequency)))
.build();
wearableExtender.addPage(averagePage);
if (hasNote) {
Notification notePage = new NotificationCompat.Builder(this)
.setContentTitle(getString(R.string.detail_note_label))
.setContentText(note)
.setStyle(new NotificationCompat.BigTextStyle()
.setBigContentTitle(getString(R.string.detail_note_label))
.bigText(note))
.build();
wearableExtender.addPage(notePage);
}
// Add 'Add Note'/'Edit Note' action
int noteIconResId = hasNote ? R.drawable.ic_notif_action_edit :
R.drawable.ic_notif_action_add;
int wearIconResId = hasNote ? R.drawable.ic_wear_action_edit :
R.drawable.ic_wear_action_add;
String noteTitle = hasNote ? getString(R.string.note_dialog_title_edit) :
getString(R.string.note_dialog_title_add);
Intent noteIntent = new Intent(this, NoteNoDisplayActivity.class);
PendingIntent notePendingIntent = PendingIntent.getActivity(this, 0, noteIntent, 0);
RemoteInput remoteInput = new RemoteInput.Builder(Intent.EXTRA_TEXT).setLabel(noteTitle).build();
builder.addAction(noteIconResId, noteTitle, notePendingIntent);
wearableExtender.addAction(new NotificationCompat.Action.Builder(wearIconResId, noteTitle,
notePendingIntent).addRemoteInput(remoteInput).build());
builder.setPublicVersion(publicBuilder.build());
notificationManager.notify(NOTIFICATION_ID, builder.extend(wearableExtender).build());
}
}