1
0
Fork 0
mirror of https://github.com/syncthing/syncthing-android.git synced 2024-12-01 16:51:16 +00:00

Add option to trigger sync every hour (fixes #15) (#387)

* AndroidManifest: Add SyncTriggerJobService

* Add Constants#isRunningOnEmulator

* Add Constants: WAIT_FOR_NEXT_SYNC_DELAY_SECS, TRIGGERED_SYNC_DURATION_SECS

* WIP: Schedule Job in BootReceiver

* Add Util/JobUtils

* Util/JobUtils: Improve log text

* Add service/SyncTriggerJobService

scheduled by JobScheduler. See JobUtils#scheduleSyncTriggerServiceJob for more details.

* RunConditionMonitor: Add SyncTriggerReceiver

via LocalBroadcastReceiver.

* BootReceiver: Add ToDo

* Add pref: PREF_RUN_ON_TIME_SCHEDULE

* Fine tune debug constants - time intervals

* JobUtils: In seconds please

* Add strings: en-GB

* RunConditionMonitor: Implement hourly sync time frames (fixes #15)

* Imported translation: de

* Fix lint: .JOB_SCHEDULER_SERVICE, API 21 instead of 23

* JobUtils: Noop on Android API level before 21 (L)

* Fix lint: RequiresApi(21) for SyncTriggerJobService

* Hide pref "run on time schedule" on Android < 5.x

* JobUtils: Show time of grace in brackets when logged

* BootReceiver: Realign comment, remove unnecessary code
This commit is contained in:
Catfriend1 2019-03-29 01:07:56 +01:00 committed by GitHub
parent 3e248fa2b3
commit 976d9f9bad
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 192 additions and 0 deletions

View file

@ -147,6 +147,11 @@
android:value=".activities.MainActivity" />
</activity>
<service android:name=".service.SyncthingService" />
<service
android:name=".service.SyncTriggerJobService"
android:label="SyncTriggerJobService"
android:permission="android.permission.BIND_JOB_SERVICE" >
</service>
<receiver android:name=".receiver.BootReceiver">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />

View file

@ -298,6 +298,11 @@ public class SettingsActivity extends SyncthingActivity {
);
mCategoryRunConditions = (PreferenceScreen) findPreference("category_run_conditions");
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
// Remove pref as we use JobScheduler implementation which is not available on API < 21.
CheckBoxPreference prefRunOnTimeSchedule = (CheckBoxPreference) findPreference(Constants.PREF_RUN_ON_TIME_SCHEDULE);
mCategoryRunConditions.removePreference(prefRunOnTimeSchedule);
}
setPreferenceCategoryChangeListener(mCategoryRunConditions, this::onRunConditionPreferenceChange);
/* Behaviour */

View file

@ -18,6 +18,10 @@ public class BootReceiver extends BroadcastReceiver {
private static final String TAG = "BootReceiver";
/**
* For testing purposes:
* adb root & adb shell am broadcast -a android.intent.action.BOOT_COMPLETED
*/
@Override
public void onReceive(Context context, Intent intent) {
Boolean bootCompleted = intent.getAction().equals(Intent.ACTION_BOOT_COMPLETED);

View file

@ -27,6 +27,7 @@ public class Constants {
public static final String PREF_RESPECT_BATTERY_SAVING = "respect_battery_saving";
public static final String PREF_RESPECT_MASTER_SYNC = "respect_master_sync";
public static final String PREF_RUN_IN_FLIGHT_MODE = "run_in_flight_mode";
public static final String PREF_RUN_ON_TIME_SCHEDULE = "run_on_time_schedule";
// Preferences - Behaviour
public static final String PREF_USE_ROOT = "use_root";
@ -123,6 +124,13 @@ public class Constants {
: 5
);
/**
* If the user enabled hourly one-time shot sync, the following
* parameters are effective.
*/
public static final int WAIT_FOR_NEXT_SYNC_DELAY_SECS = isRunningOnEmulator() ? 10 : 3600;
public static final int TRIGGERED_SYNC_DURATION_SECS = isRunningOnEmulator() ? 20 : 300;
/**
* Directory where config is exported to and imported from.
*/

View file

@ -19,11 +19,13 @@ import android.os.Handler;
import android.os.Looper;
import android.os.PowerManager;
import android.support.annotation.Nullable;
import android.support.v4.content.LocalBroadcastManager;
import android.util.Log;
import com.nutomic.syncthingandroid.R;
import com.nutomic.syncthingandroid.SyncthingApp;
import com.nutomic.syncthingandroid.service.ReceiverManager;
import com.nutomic.syncthingandroid.util.JobUtils;
import java.util.HashSet;
import java.util.Set;
@ -41,6 +43,9 @@ public class RunConditionMonitor {
private Boolean ENABLE_VERBOSE_LOG = false;
public static final String ACTION_SYNC_TRIGGER_FIRED =
"com.github.catfriend1.syncthingandroid.service.RunConditionMonitor.ACTION_SYNC_TRIGGER_FIRED";
private static final String POWER_SOURCE_CHARGER_BATTERY = "ac_and_battery_power";
private static final String POWER_SOURCE_CHARGER = "ac_power";
private static final String POWER_SOURCE_BATTERY = "battery_power";
@ -87,9 +92,17 @@ public class RunConditionMonitor {
private final Context mContext;
private ReceiverManager mReceiverManager;
private @Nullable SyncTriggerReceiver mSyncTriggerReceiver = null;
private Resources res;
private String mRunDecisionExplanation = "";
/**
* Only relevant if the user has enabled turning Syncthing on by
* time schedule for a specific amount of time periodically.
* Holds true if we are within a "SyncthingNative should run" time frame.
*/
private Boolean mTimeConditionMatch = false;
@Inject
SharedPreferences mPreferences;
@ -142,16 +155,31 @@ public class RunConditionMonitor {
mSyncStatusObserverHandle = ContentResolver.addStatusChangeListener(
ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS, mSyncStatusObserver);
// SyncTriggerReceiver
LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(mContext);
mSyncTriggerReceiver = new SyncTriggerReceiver();
localBroadcastManager.registerReceiver(mSyncTriggerReceiver,
new IntentFilter(ACTION_SYNC_TRIGGER_FIRED));
// Initially determine if syncthing should run under current circumstances.
updateShouldRunDecision();
// Initially schedule the SyncTrigger job.
JobUtils.scheduleSyncTriggerServiceJob(context, Constants.WAIT_FOR_NEXT_SYNC_DELAY_SECS);
}
public void shutdown() {
LogV("Shutting down");
JobUtils.cancelAllScheduledJobs(mContext);
if (mSyncStatusObserverHandle != null) {
ContentResolver.removeStatusChangeListener(mSyncStatusObserverHandle);
mSyncStatusObserverHandle = null;
}
if (mSyncTriggerReceiver != null) {
LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(mContext);
localBroadcastManager.unregisterReceiver(mSyncTriggerReceiver);
mSyncTriggerReceiver = null;
}
mReceiverManager.unregisterAllReceivers(mContext);
}
@ -183,6 +211,38 @@ public class RunConditionMonitor {
}
}
private class SyncTriggerReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
LogV("SyncTriggerReceiver: onReceive");
boolean prefRunOnTimeSchedule = mPreferences.getBoolean(Constants.PREF_RUN_ON_TIME_SCHEDULE, false);
if (!prefRunOnTimeSchedule) {
mTimeConditionMatch = false;
} else {
/**
* Toggle the "digital input" for this condition as the condition change is
* triggered by a time schedule and not the OS notifying us.
*/
mTimeConditionMatch = !mTimeConditionMatch;
updateShouldRunDecision();
}
/**
* Reschedule the job.
* If we are within a "SyncthingNative should run" time frame,
* let the receiver fire and change to "SyncthingNative shouldn't run" after
* TRIGGERED_SYNC_DURATION_SECS seconds elapsed.
* If we are within a "SyncthingNative shouldn't run" time frame,
* let the receiver fire and change to "SyncthingNative should run" after
* WAIT_FOR_NEXT_SYNC_DELAY_SECS seconds elapsed.
*/
JobUtils.scheduleSyncTriggerServiceJob(
context,
mTimeConditionMatch ? Constants.TRIGGERED_SYNC_DURATION_SECS : Constants.WAIT_FOR_NEXT_SYNC_DELAY_SECS
);
}
}
/**
* Event handler that is fired after preconditions changed.
* We then need to decide if syncthing should run.
@ -301,6 +361,15 @@ public class RunConditionMonitor {
boolean prefRespectPowerSaving = mPreferences.getBoolean(Constants.PREF_RESPECT_BATTERY_SAVING, true);
boolean prefRespectMasterSync = mPreferences.getBoolean(Constants.PREF_RESPECT_MASTER_SYNC, false);
boolean prefRunInFlightMode = mPreferences.getBoolean(Constants.PREF_RUN_IN_FLIGHT_MODE, false);
boolean prefRunOnTimeSchedule = mPreferences.getBoolean(Constants.PREF_RUN_ON_TIME_SCHEDULE, false);
// PREF_RUN_ON_TIME_SCHEDULE
if (prefRunOnTimeSchedule && !mTimeConditionMatch) {
// Currently, we aren't within a "SyncthingNative should run" time frame.
LogV("decideShouldRun: PREF_RUN_ON_TIME_SCHEDULE && !mTimeConditionMatch");
mRunDecisionExplanation = res.getString(R.string.reason_not_within_time_frame);
return false;
}
// PREF_POWER_SOURCE
switch (prefPowerSource) {

View file

@ -0,0 +1,37 @@
package com.nutomic.syncthingandroid.service;
import android.app.job.JobParameters;
import android.app.job.JobService;
import android.content.Context;
import android.content.Intent;
import android.support.annotation.RequiresApi;
import android.support.v4.content.LocalBroadcastManager;
// import android.util.Log;
import com.nutomic.syncthingandroid.service.Constants;
import com.nutomic.syncthingandroid.service.RunConditionMonitor;
import com.nutomic.syncthingandroid.util.JobUtils;
/**
* SyncTriggerJobService to be scheduled by the JobScheduler.
* See {@link JobUtils#scheduleSyncTriggerServiceJob} for more details.
*/
@RequiresApi(21)
public class SyncTriggerJobService extends JobService {
private static final String TAG = "SyncTriggerJobService";
@Override
public boolean onStartJob(JobParameters params) {
// Log.v(TAG, "onStartJob: Job fired.");
Context context = getApplicationContext();
LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(context);
Intent intent = new Intent(RunConditionMonitor.ACTION_SYNC_TRIGGER_FIRED);
localBroadcastManager.sendBroadcast(intent);
return true;
}
@Override
public boolean onStopJob(JobParameters params) {
return true;
}
}

View file

@ -0,0 +1,49 @@
package com.nutomic.syncthingandroid.util;
import android.annotation.TargetApi;
import android.app.job.JobInfo;
import android.app.job.JobScheduler;
import android.content.ComponentName;
import android.content.Context;
import android.os.Build;
import android.util.Log;
import com.nutomic.syncthingandroid.service.SyncTriggerJobService;
public class JobUtils {
private static final String TAG = "JobUtils";
private static final int TOLERATED_INACCURACY_IN_SECONDS = 120;
@TargetApi(21)
public static void scheduleSyncTriggerServiceJob(Context context, int delayInSeconds) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
return;
}
ComponentName serviceComponent = new ComponentName(context, SyncTriggerJobService.class);
JobInfo.Builder builder = new JobInfo.Builder(0, serviceComponent);
// Wait at least "delayInSeconds".
builder.setMinimumLatency(delayInSeconds * 1000);
// Maximum tolerated delay.
builder.setOverrideDeadline((delayInSeconds + TOLERATED_INACCURACY_IN_SECONDS) * 1000);
// Schedule the start of "SyncTriggerJobService" in "X" seconds.
JobScheduler jobScheduler = (JobScheduler) context.getSystemService(context.JOB_SCHEDULER_SERVICE);
jobScheduler.schedule(builder.build());
Log.i(TAG, "Scheduled SyncTriggerJobService to run in " +
Integer.toString(delayInSeconds) +
"(+" + Integer.toString(TOLERATED_INACCURACY_IN_SECONDS) + ") seconds.");
}
@TargetApi(21)
public static void cancelAllScheduledJobs(Context context) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
return;
}
JobScheduler jobScheduler = (JobScheduler) context.getSystemService(context.JOB_SCHEDULER_SERVICE);
jobScheduler.cancelAll();
}
}

View file

@ -454,6 +454,9 @@ Bitte melden Sie auftretende Probleme via GitHub.</string>
<string name="run_in_flight_mode_title">Starte ohne Netzwerkverbindung</string>
<string name="run_in_flight_mode_summary">Durch Aktivieren der Option wird Syncthing auch dann laufen, wenn Du offline bist. Aktiviere dies, wenn dein Telefon Probleme beim Erkennen von manuell hergestellten WLAN-Verbindungen im Flugmodus hat.</string>
<string name="run_on_time_schedule_title">Gemäß Zeitplan synchronisieren</string>
<string name="run_on_time_schedule_summary">Durch Aktivieren dieser Option wird versucht, stündlich für 5 Minuten zu synchronisieren. Dies kann eine Menge Batterie einsparen, erfordert jedoch, dass Synchronisierungspartner online sind. Hinweis: Dies kann unvollständige temporäre Dateien zurücklassen, bis die nächste geplante Synchronisierung stattfindet und abgeschlossen ist.</string>
<!-- Preferences - Behaviour -->
<string name="behaviour_autostart_title">Autostart</string>
<string name="behaviour_autostart_summary">Starte die App automatisch beim Hochfahren.</string>
@ -760,6 +763,7 @@ Bitte melden Sie auftretende Probleme via GitHub.</string>
<!-- RunConditionMonitor -->
<!-- Explanations why syncthing is running or not running -->
<string name="reason_not_within_time_frame">Sie haben \'Gemäß Zeitplan synchronisieren\' aktiviert, und die letzte Synchronisierung ist nicht länger als eine Stunde her.</string>
<string name="reason_not_charging">Telefon wird nicht aufgeladen</string>
<string name="reason_not_on_battery_power">Telefon wird nicht batteriebetrieben</string>
<string name="reason_not_while_power_saving">Syncthing läuft nicht, weil das Telefon im Energiesparmodus ist.</string>

View file

@ -457,6 +457,9 @@ Please report any problems you encounter via Github.</string>
<string name="run_in_flight_mode_title">Run without network connection</string>
<string name="run_in_flight_mode_summary">Enabling this option will cause Syncthing to run even when you\'re offline. Enable if your device has problems detecting manual Wi-Fi connections during flight mode.</string>
<string name="run_on_time_schedule_title">Run according to time schedule</string>
<string name="run_on_time_schedule_summary">Enabling this option will attempt to sync every hour for the duration of 5 minutes. This can save a lot of battery but requires sync partners to be online. Please note: This may leave incomplete temporary files behind until the next scheduled sync takes place.</string>
<!-- Preferences - Behaviour -->
<string name="behaviour_autostart_title">Autostart</string>
<string name="behaviour_autostart_summary">Start app automatically on operating system startup.</string>
@ -781,6 +784,7 @@ Please report any problems you encounter via Github.</string>
<!-- RunConditionMonitor -->
<!-- Explanations why syncthing is running or not running -->
<string name="reason_not_within_time_frame">You enabled \'Run according to time schedule\' and the last sync was no more than an hour ago.</string>
<string name="reason_not_charging">Phone is not charging.</string>
<string name="reason_not_on_battery_power">Phone is not running on battery power.</string>
<string name="reason_not_while_power_saving">Syncthing is not running as the phone is currently power saving.</string>

View file

@ -72,6 +72,13 @@
android:summary="@string/run_in_flight_mode_summary"
android:defaultValue="false" />
<!-- Run on time schedule -->
<CheckBoxPreference
android:key="run_on_time_schedule"
android:title="@string/run_on_time_schedule_title"
android:summary="@string/run_on_time_schedule_summary"
android:defaultValue="false" />
</PreferenceScreen>
<PreferenceScreen