1
0
Fork 0
mirror of https://github.com/M66B/FairEmail.git synced 2024-12-27 02:07:12 +00:00

Added simple scheduling

This commit is contained in:
M66B 2019-02-14 12:28:03 +00:00
parent 084265a1ce
commit 041f2ca3d4
5 changed files with 254 additions and 33 deletions

View file

@ -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)));
}
}

View file

@ -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

View file

@ -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) {

View file

@ -57,17 +57,65 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/swEnabled" />
<androidx.appcompat.widget.SwitchCompat
android:id="@+id/swUpdates"
android:layout_width="match_parent"
<TextView
android:id="@+id/tvSchedule"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginTop="12dp"
android:layout_marginEnd="12dp"
android:text="@string/title_advanced_updates"
android:text="@string/title_advanced_schedule"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
android:textColor="?android:attr/textColorPrimary"
app:layout_constraintBottom_toBottomOf="@+id/tvScheduleStart"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tvEnabledHint"
app:switchPadding="12dp" />
app:layout_constraintTop_toTopOf="@+id/tvScheduleStart" />
<TextView
android:id="@+id/tvScheduleStart"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="6dp"
android:padding="12dp"
android:text="00:00"
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
android:textColor="?android:attr/textColorPrimary"
app:layout_constraintEnd_toStartOf="@+id/tvScheduleSeparator"
app:layout_constraintTop_toBottomOf="@id/tvEnabledHint" />
<TextView
android:id="@+id/tvScheduleSeparator"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="6dp"
android:text="—"
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
android:textColor="?android:attr/textColorPrimary"
app:layout_constraintBottom_toBottomOf="@+id/tvScheduleStart"
app:layout_constraintEnd_toStartOf="@+id/tvScheduleEnd"
app:layout_constraintTop_toTopOf="@+id/tvScheduleStart" />
<TextView
android:id="@+id/tvScheduleEnd"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="12dp"
android:text="00:00"
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
android:textColor="?android:attr/textColorPrimary"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/tvEnabledHint" />
<TextView
android:id="@+id/tvScheduleHint"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginEnd="60dp"
android:text="@string/title_advanced_schedule_hint"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
android:textStyle="italic"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tvScheduleEnd" />
<TextView
android:id="@+id/tvSectionConnection"
@ -78,7 +126,7 @@
android:text="@string/title_advanced_section_connection"
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/swUpdates" />
app:layout_constraintTop_toBottomOf="@id/tvScheduleHint" />
<View
android:id="@+id/vSeparatorConnection"
@ -680,6 +728,18 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/swLight" />
<androidx.appcompat.widget.SwitchCompat
android:id="@+id/swUpdates"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginTop="12dp"
android:layout_marginEnd="12dp"
android:text="@string/title_advanced_updates"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/btnSound"
app:switchPadding="12dp" />
<androidx.appcompat.widget.SwitchCompat
android:id="@+id/swDebug"
android:layout_width="match_parent"
@ -690,7 +750,7 @@
android:layout_marginBottom="12dp"
android:text="@string/title_advanced_debug"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/btnSound"
app:layout_constraintTop_toBottomOf="@id/swUpdates"
app:switchPadding="12dp" />
<androidx.constraintlayout.widget.Group

View file

@ -141,7 +141,8 @@
<string name="title_advanced_section_misc">Miscellaneous</string>
<string name="title_advanced_enabled">Synchronize</string>
<string name="title_advanced_updates">Check for updates</string>
<string name="title_advanced_schedule">Schedule</string>
<string name="title_advanced_schedule_hint">Tap on a time to set a time</string>
<string name="title_advanced_metered">Use metered connections</string>
<string name="title_advanced_download">Automatically download messages and attachments on a metered connection up to</string>
@ -174,6 +175,7 @@
<string name="title_advanced_notify_preview">Show message preview in notifications</string>
<string name="title_advanced_light">Use notification light</string>
<string name="title_advanced_sound">Select notification sound</string>
<string name="title_advanced_updates">Check for updates</string>
<string name="title_advanced_debug">Debug mode</string>
<string name="title_advanced_enabled_hint">Globally disable or enable receiving and sending of messages</string>