diff --git a/app/src/main/java/eu/faircode/email/FragmentOptions.java b/app/src/main/java/eu/faircode/email/FragmentOptions.java
index 10d5485b16..cd97ceaa10 100644
--- a/app/src/main/java/eu/faircode/email/FragmentOptions.java
+++ b/app/src/main/java/eu/faircode/email/FragmentOptions.java
@@ -19,6 +19,8 @@ package eu.faircode.email;
Copyright 2018-2019 by Marcel Bokhorst (M66B)
*/
+import android.app.Dialog;
+import android.app.TimePickerDialog;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
@@ -31,6 +33,7 @@ import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.preference.PreferenceManager;
+import android.text.format.DateFormat;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
@@ -42,13 +45,17 @@ import android.widget.Button;
import android.widget.CompoundButton;
import android.widget.Spinner;
import android.widget.TextView;
+import android.widget.TimePicker;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
import java.util.List;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.SwitchCompat;
import androidx.constraintlayout.widget.Group;
+import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.FragmentActivity;
import androidx.lifecycle.Lifecycle;
@@ -56,7 +63,8 @@ import static android.app.Activity.RESULT_OK;
public class FragmentOptions extends FragmentBase implements SharedPreferences.OnSharedPreferenceChangeListener {
private SwitchCompat swEnabled;
- private SwitchCompat swUpdates;
+ private TextView tvScheduleStart;
+ private TextView tvScheduleEnd;
private TextView tvConnectionType;
private SwitchCompat swMetered;
@@ -90,6 +98,7 @@ public class FragmentOptions extends FragmentBase implements SharedPreferences.O
private SwitchCompat swLight;
private Button btnSound;
+ private SwitchCompat swUpdates;
private SwitchCompat swDebug;
private Group grpNotification;
@@ -101,13 +110,13 @@ public class FragmentOptions extends FragmentBase implements SharedPreferences.O
};
private final static String[] ADVANCED_OPTIONS = new String[]{
- "enabled", "updates",
+ "enabled", "schedule_start", "schedule_end",
"metered", "download",
"unified", "date", "threading", "avatars", "identicons", "name_email", "preview", "addresses", "autoimages", "actionbar",
"pull", "swipenav", "autoexpand", "autoclose", "autonext", "collapse", "autoread", "automove",
"autoresize", "sender", "autosend",
"notify_preview", "light", "sound",
- "debug",
+ "updates", "debug",
"first", "why", "last_update_check", "app_support", "message_swipe", "message_select", "folder_actions", "folder_sync",
"edit_ref_confirmed", "show_html_confirmed", "show_images_confirmed"
};
@@ -122,7 +131,8 @@ public class FragmentOptions extends FragmentBase implements SharedPreferences.O
// Get controls
swEnabled = view.findViewById(R.id.swEnabled);
- swUpdates = view.findViewById(R.id.swUpdates);
+ tvScheduleStart = view.findViewById(R.id.tvScheduleStart);
+ tvScheduleEnd = view.findViewById(R.id.tvScheduleEnd);
tvConnectionType = view.findViewById(R.id.tvConnectionType);
swMetered = view.findViewById(R.id.swMetered);
@@ -156,6 +166,7 @@ public class FragmentOptions extends FragmentBase implements SharedPreferences.O
swLight = view.findViewById(R.id.swLight);
btnSound = view.findViewById(R.id.btnSound);
+ swUpdates = view.findViewById(R.id.swUpdates);
swDebug = view.findViewById(R.id.swDebug);
grpNotification = view.findViewById(R.id.grpNotification);
@@ -174,6 +185,28 @@ public class FragmentOptions extends FragmentBase implements SharedPreferences.O
}
});
+ tvScheduleStart.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ Bundle args = new Bundle();
+ args.putBoolean("start", true);
+ DialogFragment timePicker = new TimePickerFragment();
+ timePicker.setArguments(args);
+ timePicker.show(getFragmentManager(), "timePicker");
+ }
+ });
+
+ tvScheduleEnd.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ Bundle args = new Bundle();
+ args.putBoolean("start", false);
+ DialogFragment timePicker = new TimePickerFragment();
+ timePicker.setArguments(args);
+ timePicker.show(getFragmentManager(), "timePicker");
+ }
+ });
+
swUpdates.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton compoundButton, boolean checked) {
@@ -480,8 +513,9 @@ public class FragmentOptions extends FragmentBase implements SharedPreferences.O
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getContext());
swEnabled.setChecked(prefs.getBoolean("enabled", true));
- swUpdates.setChecked(prefs.getBoolean("updates", true));
- swUpdates.setVisibility(Helper.isPlayStoreInstall(getContext()) ? View.GONE : View.VISIBLE);
+
+ tvScheduleStart.setText(formatHour(prefs.getInt("schedule_start", 0)));
+ tvScheduleEnd.setText(formatHour(prefs.getInt("schedule_end", 0)));
swMetered.setChecked(prefs.getBoolean("metered", true));
@@ -524,11 +558,55 @@ public class FragmentOptions extends FragmentBase implements SharedPreferences.O
swNotifyPreview.setChecked(prefs.getBoolean("notify_preview", true));
swLight.setChecked(prefs.getBoolean("light", false));
+ swUpdates.setChecked(prefs.getBoolean("updates", true));
+ swUpdates.setVisibility(Helper.isPlayStoreInstall(getContext()) ? View.GONE : View.VISIBLE);
swDebug.setChecked(prefs.getBoolean("debug", false));
grpNotification.setVisibility(BuildConfig.DEBUG || Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.O ? View.VISIBLE : View.GONE);
}
+ private String formatHour(int minutes) {
+ Calendar cal = Calendar.getInstance();
+ cal.set(Calendar.HOUR_OF_DAY, minutes / 60);
+ cal.set(Calendar.MINUTE, minutes % 60);
+ cal.set(Calendar.SECOND, 0);
+ cal.set(Calendar.MILLISECOND, 0);
+ return SimpleDateFormat.getTimeInstance(SimpleDateFormat.SHORT).format(cal.getTime());
+ }
+
+ public static class TimePickerFragment extends DialogFragment implements TimePickerDialog.OnTimeSetListener {
+ @NonNull
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ Bundle args = getArguments();
+ boolean start = args.getBoolean("start");
+
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getContext());
+ int minutes = prefs.getInt("schedule_" + (start ? "start" : "end"), 0);
+
+ Calendar cal = Calendar.getInstance();
+ cal.set(Calendar.HOUR_OF_DAY, minutes / 60);
+ cal.set(Calendar.MINUTE, minutes % 60);
+ cal.set(Calendar.SECOND, 0);
+ cal.set(Calendar.MILLISECOND, 0);
+
+ return new TimePickerDialog(getActivity(), this,
+ cal.get(Calendar.HOUR_OF_DAY),
+ cal.get(Calendar.MINUTE),
+ DateFormat.is24HourFormat(getActivity()));
+ }
+
+ public void onTimeSet(TimePicker view, int hour, int minute) {
+ Bundle args = getArguments();
+ boolean start = args.getBoolean("start");
+
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getContext());
+ prefs.edit().putInt("schedule_" + (start ? "start" : "end"), hour * 60 + minute).apply();
+
+ ServiceSynchronize.schedule(getContext());
+ }
+ }
+
private ConnectivityManager.NetworkCallback networkCallback = new ConnectivityManager.NetworkCallback() {
@Override
public void onAvailable(Network network) {
@@ -587,5 +665,9 @@ public class FragmentOptions extends FragmentBase implements SharedPreferences.O
public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
if ("enabled".equals(key))
swEnabled.setChecked(prefs.getBoolean(key, true));
+ else if ("schedule_start".equals(key))
+ tvScheduleStart.setText(formatHour(prefs.getInt(key, 0)));
+ else if ("schedule_end".equals(key))
+ tvScheduleEnd.setText(formatHour(prefs.getInt(key, 0)));
}
}
diff --git a/app/src/main/java/eu/faircode/email/JobDaily.java b/app/src/main/java/eu/faircode/email/JobDaily.java
index 4ab051d0ce..71545e3610 100644
--- a/app/src/main/java/eu/faircode/email/JobDaily.java
+++ b/app/src/main/java/eu/faircode/email/JobDaily.java
@@ -42,19 +42,22 @@ public class JobDaily extends JobService {
private static final long CACHE_IMAGE_DURATION = 3 * 24 * 3600 * 1000L; // milliseconds
private static final long KEEP_LOG_DURATION = 24 * 3600 * 1000L; // milliseconds
- public static void schedule(Context context) {
- Log.i("Scheduling daily job");
+ public static void schedule(Context context, boolean enabled) {
+ JobScheduler scheduler = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
JobInfo.Builder job = new JobInfo.Builder(Helper.JOB_DAILY, new ComponentName(context, JobDaily.class))
.setPeriodic(CLEANUP_INTERVAL)
.setRequiresDeviceIdle(true);
- JobScheduler scheduler = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
- scheduler.cancel(Helper.JOB_DAILY);
- if (scheduler.schedule(job.build()) == JobScheduler.RESULT_SUCCESS)
- Log.i("Scheduled daily job");
- else
- Log.e("Scheduling daily job failed");
+ if (enabled)
+ if (scheduler.schedule(job.build()) == JobScheduler.RESULT_SUCCESS)
+ Log.i("Scheduled daily job");
+ else
+ Log.e("Scheduling daily job failed");
+ else {
+ Log.i("Cancelled daily job");
+ scheduler.cancel(Helper.JOB_DAILY);
+ }
}
@Override
diff --git a/app/src/main/java/eu/faircode/email/ServiceSynchronize.java b/app/src/main/java/eu/faircode/email/ServiceSynchronize.java
index 5c35655057..12daaddde0 100644
--- a/app/src/main/java/eu/faircode/email/ServiceSynchronize.java
+++ b/app/src/main/java/eu/faircode/email/ServiceSynchronize.java
@@ -163,6 +163,7 @@ public class ServiceSynchronize extends LifecycleService {
static final int PI_TRASH = 6;
static final int PI_IGNORED = 7;
static final int PI_SNOOZED = 8;
+ static final int PI_SCHEDULE = 9;
@Override
public void onCreate() {
@@ -304,6 +305,7 @@ public class ServiceSynchronize extends LifecycleService {
public int onStartCommand(Intent intent, int flags, int startId) {
String action = (intent == null ? null : intent.getAction());
Log.i("Service command intent=" + intent + " action=" + action);
+ Log.logExtras(intent);
startForeground(NOTIFICATION_SYNCHRONIZE, getNotificationService(null).build());
@@ -335,6 +337,10 @@ public class ServiceSynchronize extends LifecycleService {
serviceManager.service_init();
break;
+ case "schedule":
+ serviceManager.service_schedule();
+ break;
+
case "reload":
serviceManager.service_reload(intent.getStringExtra("reason"));
break;
@@ -3015,6 +3021,18 @@ public class ServiceSynchronize extends LifecycleService {
private void service_init() {
EntityLog.log(ServiceSynchronize.this, "Service init");
+
+ next_schedule();
+
+ boolean enabled = isEnabled();
+ JobDaily.schedule(ServiceSynchronize.this, enabled);
+ if (!enabled)
+ stopSelf();
+ }
+
+ private void service_schedule() {
+ next_schedule();
+ service_reload("schedule");
}
private void service_reload(String reason) {
@@ -3033,6 +3051,8 @@ public class ServiceSynchronize extends LifecycleService {
if (started)
queue_reload(false, "service destroy");
}
+
+ JobDaily.schedule(ServiceSynchronize.this, false);
}
private void start() {
@@ -3209,11 +3229,63 @@ public class ServiceSynchronize extends LifecycleService {
state = null;
}
+ private void next_schedule() {
+ Intent schedule = new Intent(ServiceSynchronize.this, ServiceSynchronize.class);
+ schedule.setAction("schedule");
+ PendingIntent piSchedule = PendingIntent.getService(
+ ServiceSynchronize.this, PI_SCHEDULE, schedule, PendingIntent.FLAG_UPDATE_CURRENT);
+
+ AlarmManager am = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
+ am.cancel(piSchedule);
+
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(ServiceSynchronize.this);
+ int minuteStart = prefs.getInt("schedule_start", 0);
+ int minuteEnd = prefs.getInt("schedule_end", 0);
+
+ if (minuteEnd <= minuteStart)
+ minuteEnd += 24 * 60;
+
+ Calendar calStart = Calendar.getInstance();
+ calStart.set(Calendar.HOUR_OF_DAY, minuteStart / 60);
+ calStart.set(Calendar.MINUTE, minuteStart % 60);
+ calStart.set(Calendar.SECOND, 0);
+ calStart.set(Calendar.MILLISECOND, 0);
+
+ Calendar calEnd = Calendar.getInstance();
+ calEnd.set(Calendar.HOUR_OF_DAY, minuteEnd / 60);
+ calEnd.set(Calendar.MINUTE, minuteEnd % 60);
+ calEnd.set(Calendar.SECOND, 0);
+ calEnd.set(Calendar.MILLISECOND, 0);
+
+ long now = new Date().getTime();
+ if (now > calEnd.getTimeInMillis()) {
+ calStart.set(Calendar.DAY_OF_MONTH, calStart.get(Calendar.DAY_OF_MONTH) + 1);
+ calEnd.set(Calendar.DAY_OF_MONTH, calEnd.get(Calendar.DAY_OF_MONTH) + 1);
+ }
+
+ long start = calStart.getTimeInMillis();
+ long end = calEnd.getTimeInMillis();
+ long next = (now < start ? start : end);
+
+ EntityLog.log(ServiceSynchronize.this, "Schedule now=" + new Date(now));
+ EntityLog.log(ServiceSynchronize.this, "Schedule start=" + new Date(start));
+ EntityLog.log(ServiceSynchronize.this, "Schedule end=" + new Date(end));
+ EntityLog.log(ServiceSynchronize.this, "Schedule next=" + new Date(next));
+
+ boolean enabled = (now >= start && now < end);
+ prefs.edit().putBoolean("enabled", enabled).apply();
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
+ am.setAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, next, piSchedule);
+ else
+ am.set(AlarmManager.RTC_WAKEUP, next, piSchedule);
+ }
+
private void queue_reload(final boolean start, final String reason) {
final boolean doStop = started;
final boolean doStart = (start && isEnabled() && suitableNetwork());
- EntityLog.log(ServiceSynchronize.this, "Queue reload " +
+ EntityLog.log(ServiceSynchronize.this, "Queue reload" +
" doStop=" + doStop + " doStart=" + doStart + " queued=" + queued + " " + reason);
started = doStart;
@@ -3276,13 +3348,15 @@ public class ServiceSynchronize extends LifecycleService {
}
public static void init(Context context) {
- SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
- if (prefs.getBoolean("enabled", true)) {
- ContextCompat.startForegroundService(context,
- new Intent(context, ServiceSynchronize.class)
- .setAction("init"));
- JobDaily.schedule(context);
- }
+ ContextCompat.startForegroundService(context,
+ new Intent(context, ServiceSynchronize.class)
+ .setAction("init"));
+ }
+
+ public static void schedule(Context context) {
+ ContextCompat.startForegroundService(context,
+ new Intent(context, ServiceSynchronize.class)
+ .setAction("schedule"));
}
public static void reload(Context context, String reason) {
diff --git a/app/src/main/res/layout/fragment_options.xml b/app/src/main/res/layout/fragment_options.xml
index 699c86bf0d..4f6174f1c5 100644
--- a/app/src/main/res/layout/fragment_options.xml
+++ b/app/src/main/res/layout/fragment_options.xml
@@ -57,17 +57,65 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/swEnabled" />
-
+ app:layout_constraintTop_toTopOf="@+id/tvScheduleStart" />
+
+
+
+
+
+
+
+
+ app:layout_constraintTop_toBottomOf="@id/tvScheduleHint" />
+
+
Miscellaneous
Synchronize
- Check for updates
+ Schedule
+ Tap on a time to set a time
Use metered connections
Automatically download messages and attachments on a metered connection up to
@@ -174,6 +175,7 @@
Show message preview in notifications
Use notification light
Select notification sound
+ Check for updates
Debug mode
Globally disable or enable receiving and sending of messages