package de.geeksfactory.opacclient.reminder;
import android.app.AlarmManager;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Build;
import android.preference.PreferenceManager;
import android.util.Log;
import org.joda.time.LocalDate;
import org.joda.time.format.DateTimeFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import de.geeksfactory.opacclient.BuildConfig;
import de.geeksfactory.opacclient.OpacClient;
import de.geeksfactory.opacclient.objects.LentItem;
import de.geeksfactory.opacclient.storage.AccountDataSource;
public class ReminderHelper {
private OpacClient app;
private SharedPreferences sp;
private AccountDataSource data;
private boolean log;
public ReminderHelper(OpacClient app) {
this.app = app;
sp = PreferenceManager.getDefaultSharedPreferences(app);
data = new AccountDataSource(app);
log = BuildConfig.DEBUG;
}
ReminderHelper(OpacClient app, SharedPreferences sp, AccountDataSource data) {
this.app = app;
this.sp = sp;
this.data = data;
log = false;
}
/**
* Save alarms for expiring media to the DB and schedule them using {@link
* android.app.AlarmManager}.
*/
public void generateAlarms() {
generateAlarms(-1, null);
}
private void generateAlarms(int warning, Boolean enabled) {
// resets the notified field to false for all alarms with finished == false this will re-show
// notifications that were not dismissed yet (for example after reboot)
data.resetNotifiedOnAllAlarams();
if (warning == -1) warning = Integer.parseInt(sp.getString("notification_warning", "3"));
if (warning > 10) {
// updated from the old app version -> change value to get days instead of milliseconds
warning = warning / (24 * 60 * 60 * 1000);
sp.edit().putString("notification_warning", String.valueOf(warning)).apply();
}
if (enabled == null) enabled = sp.getBoolean("notification_service", false);
if (!enabled) {
if (log) {
Log.d("OpacClient",
"scheduling no alarms because notifications are disabled");
}
return;
}
List<LentItem> items = data.getAllLentItems();
// Sort lent items by deadline
Map<LocalDate, List<Long>> arrangedIds = new HashMap<>();
for (LentItem item : items) {
LocalDate deadline = item.getDeadline();
if (deadline == null) {
// Fail silently to not annoy users. We display a warning in account view in
// this case.
continue;
}
if (item.getDownloadData() != null && item.getDownloadData().startsWith("http")) {
// Don't remind people of bringing back ebooks, because ... uhm...
continue;
}
if (!arrangedIds.containsKey(deadline)) {
arrangedIds.put(deadline, new ArrayList<Long>());
}
arrangedIds.get(deadline).add(item.getDbId());
}
for (Alarm alarm : data.getAllAlarms()) {
// Remove alarms with no corresponding media
if (!arrangedIds.containsKey(alarm.deadline)) {
cancelNotification(alarm);
data.removeAlarm(alarm);
}
}
// Find and add/update corresponding alarms for current lent media
for (Map.Entry<LocalDate, List<Long>> entry : arrangedIds.entrySet()) {
LocalDate deadline = entry.getKey();
long[] media = toArray(entry.getValue());
Alarm alarm = data.getAlarmByDeadline(deadline);
if (alarm != null) {
if (!Arrays.equals(media, alarm.media)) {
alarm.media = media;
data.updateAlarm(alarm);
}
} else {
if (log) {
Log.i("OpacClient",
"scheduling alarm for " + media.length + " items with deadline on " +
DateTimeFormat.shortDate().print(deadline) + " on " +
DateTimeFormat.shortDate().print(deadline.minusDays(warning)));
}
data.addAlarm(deadline, media,
deadline.minusDays(warning).toDateTimeAtStartOfDay());
}
}
scheduleAlarms(true);
}
/**
* Update alarms when the warning period setting is changed
* @param newWarning new warning period
*/
public void updateAlarms(int newWarning) {
// We could do this better, but for now, let's simply recreate all alarms. This can
// result in some notifications being shown immediately.
cancelAllNotifications();
clearAlarms();
generateAlarms(newWarning, null);
}
/**
* Update alarms when notifications were enabled or disabled
* @param enabled Whether notification were enabled or disabled
*/
public void updateAlarms(boolean enabled) {
cancelAllNotifications();
clearAlarms();
generateAlarms(-1, enabled);
}
private void clearAlarms() {
data.clearAlarms();
}
private void cancelNotification(Alarm alarm) {
NotificationManager notificationManager = (NotificationManager) app
.getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.cancel((int) alarm.id);
}
private void cancelAllNotifications() {
NotificationManager notificationManager = (NotificationManager) app
.getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.cancelAll();
}
/**
* (re-)schedule alarms using {@link android.app.AlarmManager}
*/
public void scheduleAlarms() {
scheduleAlarms(false);
}
private void scheduleAlarms(boolean enabled) {
if (!sp.getBoolean("notification_service", false) && !enabled) return;
AlarmManager alarmManager = (AlarmManager) app.getSystemService(Context.ALARM_SERVICE);
List<Alarm> alarms = data.getAllAlarms();
for (Alarm alarm : alarms) {
if (!alarm.notified) {
Intent i = new Intent(app, ReminderBroadcastReceiver.class);
i.setAction(ReminderBroadcastReceiver.ACTION_SHOW_NOTIFICATION);
i.putExtra(ReminderBroadcastReceiver.EXTRA_ALARM_ID, alarm.id);
PendingIntent pi = PendingIntent
.getBroadcast(app, (int) alarm.id, i, PendingIntent.FLAG_UPDATE_CURRENT);
// If the alarm's timestamp is in the past, AlarmManager will trigger it
// immediately.
setExact(alarmManager, AlarmManager.RTC_WAKEUP, alarm.notificationTime.getMillis(),
pi);
}
}
}
private long[] toArray(List<Long> list) {
long[] array = new long[list.size()];
for (int i = 0; i < list.size(); i++) array[i] = list.get(i);
return array;
}
@SuppressWarnings("SameParameterValue")
private void setExact(AlarmManager am, int type, long triggerAtMillis,
PendingIntent operation) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
am.setExact(type, triggerAtMillis, operation);
} else {
am.set(type, triggerAtMillis, operation);
}
}
public void resetNotified() {
data.resetNotifiedOnAllAlarams();
}
}