Merge branch 'update-scheduling' into 'master'
modernize update scheduling See merge request fdroid/fdroidclient!416
This commit is contained in:
commit
38a3ccda17
@ -258,6 +258,10 @@
|
||||
</receiver>
|
||||
|
||||
<service android:name=".UpdateService"/>
|
||||
<service
|
||||
android:name=".UpdateJobService"
|
||||
android:exported="false"
|
||||
android:permission="android.permission.BIND_JOB_SERVICE"/>
|
||||
<service
|
||||
android:name=".net.DownloaderService"
|
||||
android:exported="false"/>
|
||||
|
@ -75,6 +75,7 @@ public final class Preferences implements SharedPreferences.OnSharedPreferenceCh
|
||||
private static final boolean DEFAULT_SHOW_INCOMPAT_VERSIONS = false;
|
||||
private static final boolean DEFAULT_SHOW_ROOT_APPS = true;
|
||||
private static final boolean DEFAULT_SHOW_ANTI_FEATURE_APPS = true;
|
||||
private static final int DEFAULT_UPD_INTERVAL = 24;
|
||||
private static final boolean DEFAULT_PRIVILEGED_INSTALLER = true;
|
||||
//private static final boolean DEFAULT_LOCAL_REPO_BONJOUR = true;
|
||||
private static final long DEFAULT_KEEP_CACHE_TIME = TimeUnit.DAYS.toMillis(1);
|
||||
@ -157,6 +158,21 @@ public final class Preferences implements SharedPreferences.OnSharedPreferenceCh
|
||||
*/
|
||||
private static final String PREF_CACHE_APK = "cacheDownloaded";
|
||||
|
||||
/**
|
||||
* Get the update interval in milliseconds.
|
||||
*/
|
||||
public int getUpdateInterval() {
|
||||
int hours;
|
||||
try {
|
||||
String value = preferences.getString(PREF_UPD_INTERVAL,
|
||||
String.valueOf(DEFAULT_UPD_INTERVAL));
|
||||
hours = Integer.parseInt(value);
|
||||
} catch (NumberFormatException e) {
|
||||
hours = DEFAULT_UPD_INTERVAL;
|
||||
}
|
||||
return hours * 60 * 60 * 1000;
|
||||
}
|
||||
|
||||
/**
|
||||
* Time in millis to keep cached files. Anything that has been around longer will be deleted
|
||||
*/
|
||||
|
43
app/src/main/java/org/fdroid/fdroid/UpdateJobService.java
Normal file
43
app/src/main/java/org/fdroid/fdroid/UpdateJobService.java
Normal file
@ -0,0 +1,43 @@
|
||||
package org.fdroid.fdroid;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.job.JobParameters;
|
||||
import android.app.job.JobService;
|
||||
import android.content.Intent;
|
||||
|
||||
/**
|
||||
* Interface between the new {@link android.app.job.JobScheduler} API and
|
||||
* our old {@link UpdateService}, which is based on {@link android.app.IntentService}.
|
||||
* This does not do things the way it should, e.g. stopping the job on
|
||||
* {@link #onStopJob(JobParameters)} and properly reporting
|
||||
* {@link #jobFinished(JobParameters, boolean)}, but this at least provides
|
||||
* the nice early triggering when there is good power/wifi available.
|
||||
*
|
||||
* @see <a href="https://developer.android.com/about/versions/android-5.0.html#Power">Project Volta: Scheduling jobs</a>
|
||||
*/
|
||||
@TargetApi(21)
|
||||
public class UpdateJobService extends JobService {
|
||||
@Override
|
||||
public boolean onStartJob(final JobParameters params) {
|
||||
new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
// faking the actually run time
|
||||
try {
|
||||
startService(new Intent(UpdateJobService.this, UpdateService.class));
|
||||
Thread.sleep(2000);
|
||||
} catch (InterruptedException e) {
|
||||
// ignored
|
||||
} finally {
|
||||
jobFinished(params, false);
|
||||
}
|
||||
}
|
||||
}.start();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onStopJob(JobParameters params) {
|
||||
return false;
|
||||
}
|
||||
}
|
@ -22,11 +22,15 @@ import android.app.AlarmManager;
|
||||
import android.app.IntentService;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.app.job.JobInfo;
|
||||
import android.app.job.JobScheduler;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Build;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
@ -51,6 +55,7 @@ import org.fdroid.fdroid.net.BluetoothDownloader;
|
||||
import org.fdroid.fdroid.net.ConnectivityMonitorService;
|
||||
import org.fdroid.fdroid.views.main.MainActivity;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@ -116,32 +121,41 @@ public class UpdateService extends IntentService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule or cancel this service to update the app index, according to the
|
||||
* current preferences. Should be called a) at boot, b) if the preference
|
||||
* is changed, or c) on startup, in case we get upgraded.
|
||||
* Schedule this service to update the app index while canceling any previously
|
||||
* scheduled updates, according to the current preferences. Should be called
|
||||
* a) at boot, b) if the preference is changed, or c) on startup, in case we get
|
||||
* upgraded. It works differently on {@code android-21} and newer, versus older,
|
||||
* due to the {@link JobScheduler} API handling it very nicely for us.
|
||||
*
|
||||
* @see <a href="https://developer.android.com/about/versions/android-5.0.html#Power">Project Volta: Scheduling jobs</a>
|
||||
*/
|
||||
public static void schedule(Context ctx) {
|
||||
public static void schedule(Context context) {
|
||||
int interval = Preferences.get().getUpdateInterval();
|
||||
|
||||
SharedPreferences prefs = PreferenceManager
|
||||
.getDefaultSharedPreferences(ctx);
|
||||
String sint = prefs.getString(Preferences.PREF_UPD_INTERVAL, "0");
|
||||
int interval = Integer.parseInt(sint);
|
||||
if (Build.VERSION.SDK_INT < 21) {
|
||||
Intent intent = new Intent(context, UpdateService.class);
|
||||
PendingIntent pending = PendingIntent.getService(context, 0, intent, 0);
|
||||
|
||||
Intent intent = new Intent(ctx, UpdateService.class);
|
||||
PendingIntent pending = PendingIntent.getService(ctx, 0, intent, 0);
|
||||
|
||||
AlarmManager alarm = (AlarmManager) ctx
|
||||
.getSystemService(Context.ALARM_SERVICE);
|
||||
AlarmManager alarm = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
|
||||
alarm.cancel(pending);
|
||||
if (interval > 0) {
|
||||
alarm.setInexactRepeating(AlarmManager.ELAPSED_REALTIME,
|
||||
SystemClock.elapsedRealtime() + 5000,
|
||||
AlarmManager.INTERVAL_HOUR, pending);
|
||||
SystemClock.elapsedRealtime() + 5000, interval, pending);
|
||||
Utils.debugLog(TAG, "Update scheduler alarm set");
|
||||
} else {
|
||||
Utils.debugLog(TAG, "Update scheduler alarm not set");
|
||||
}
|
||||
|
||||
} else {
|
||||
Utils.debugLog(TAG, "Using android-21 JobScheduler for updates");
|
||||
JobScheduler jobScheduler = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
|
||||
jobScheduler.cancelAll();
|
||||
ComponentName componentName = new ComponentName(context, UpdateJobService.class);
|
||||
JobInfo task = new JobInfo.Builder(0xfedcba, componentName)
|
||||
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED)
|
||||
.setOverrideDeadline(interval)
|
||||
.build();
|
||||
jobScheduler.schedule(task);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -152,6 +166,51 @@ public class UpdateService extends IntentService {
|
||||
return updating;
|
||||
}
|
||||
|
||||
private static volatile boolean isScheduleIfStillOnWifiRunning;
|
||||
|
||||
/**
|
||||
* Waits for a period of time for the WiFi to settle, then if the WiFi is
|
||||
* still active, it schedules an update. This is to encourage the use of
|
||||
* unlimited networks over metered networks for index updates and auto
|
||||
* downloads of app updates. Starting with {@code android-21}, this uses
|
||||
* {@link android.app.job.JobScheduler} instead.
|
||||
*/
|
||||
public static void scheduleIfStillOnWifi(Context context) {
|
||||
if (Build.VERSION.SDK_INT >= 21) {
|
||||
throw new IllegalStateException("This should never be used on android-21 or newer!");
|
||||
}
|
||||
if (isScheduleIfStillOnWifiRunning || !Preferences.get().isBackgroundDownloadAllowed()) {
|
||||
return;
|
||||
}
|
||||
isScheduleIfStillOnWifiRunning = true;
|
||||
new StillOnWifiAsyncTask(context).execute();
|
||||
}
|
||||
|
||||
private static final class StillOnWifiAsyncTask extends AsyncTask<Void, Void, Void> {
|
||||
|
||||
private final WeakReference<Context> contextWeakReference;
|
||||
|
||||
private StillOnWifiAsyncTask(Context context) {
|
||||
this.contextWeakReference = new WeakReference<>(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Void doInBackground(Void... voids) {
|
||||
Context context = contextWeakReference.get();
|
||||
try {
|
||||
Thread.sleep(120000);
|
||||
if (Preferences.get().isBackgroundDownloadAllowed()) {
|
||||
Utils.debugLog(TAG, "scheduling update because there is good internet");
|
||||
schedule(context);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Utils.debugLog(TAG, e.getMessage());
|
||||
}
|
||||
isScheduleIfStillOnWifiRunning = false;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
@ -279,35 +338,6 @@ public class UpdateService extends IntentService {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Check whether it is time to run the scheduled update.
|
||||
* We don't want to run if:
|
||||
* - The time between scheduled runs is set to zero (though don't know
|
||||
* when that would occur)
|
||||
* - Last update was too recent
|
||||
* - Not on wifi, but the property for "Only auto update on wifi" is set.
|
||||
*
|
||||
* @return True if we are due for a scheduled update.
|
||||
*/
|
||||
private boolean verifyIsTimeForScheduledRun() {
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getBaseContext());
|
||||
String sint = prefs.getString(Preferences.PREF_UPD_INTERVAL, "0");
|
||||
int interval = Integer.parseInt(sint);
|
||||
if (interval == 0) {
|
||||
Log.i(TAG, "Skipping update - disabled");
|
||||
return false;
|
||||
}
|
||||
long lastUpdate = prefs.getLong(STATE_LAST_UPDATED, 0);
|
||||
long elapsed = System.currentTimeMillis() - lastUpdate;
|
||||
if (elapsed < interval * 60 * 60 * 1000) {
|
||||
Log.i(TAG, "Skipping update - done " + elapsed
|
||||
+ "ms ago, interval is " + interval + " hours");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* In order to send a {@link Toast} from a {@link IntentService}, we have to do these tricks.
|
||||
*/
|
||||
@ -339,6 +369,7 @@ public class UpdateService extends IntentService {
|
||||
}
|
||||
|
||||
try {
|
||||
final Preferences fdroidPrefs = Preferences.get();
|
||||
// See if it's time to actually do anything yet...
|
||||
int netState = ConnectivityMonitorService.getNetworkState(this);
|
||||
if (address != null && address.startsWith(BluetoothDownloader.SCHEME)) {
|
||||
@ -349,12 +380,9 @@ public class UpdateService extends IntentService {
|
||||
sendNoInternetToast();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
final Preferences fdroidPrefs = Preferences.get();
|
||||
if (manualUpdate || forcedUpdate) {
|
||||
} else if (manualUpdate || forcedUpdate) {
|
||||
Utils.debugLog(TAG, "manually requested or forced update");
|
||||
} else if (!verifyIsTimeForScheduledRun() || !fdroidPrefs.isBackgroundDownloadAllowed()) {
|
||||
} else if (!fdroidPrefs.isBackgroundDownloadAllowed()) {
|
||||
Utils.debugLog(TAG, "don't run update");
|
||||
return;
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ import android.net.DhcpInfo;
|
||||
import android.net.NetworkInfo;
|
||||
import android.net.wifi.WifiInfo;
|
||||
import android.net.wifi.WifiManager;
|
||||
import android.os.Build;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.content.LocalBroadcastManager;
|
||||
import android.text.TextUtils;
|
||||
@ -15,6 +16,7 @@ import android.util.Log;
|
||||
import org.apache.commons.net.util.SubnetUtils;
|
||||
import org.fdroid.fdroid.FDroidApp;
|
||||
import org.fdroid.fdroid.Preferences;
|
||||
import org.fdroid.fdroid.UpdateService;
|
||||
import org.fdroid.fdroid.Utils;
|
||||
import org.fdroid.fdroid.data.Repo;
|
||||
import org.fdroid.fdroid.localrepo.LocalRepoKeyStore;
|
||||
@ -42,6 +44,11 @@ import java.util.Locale;
|
||||
* changed. Having the {@code Thread} also makes it easy to kill work
|
||||
* that is in progress.
|
||||
* <p>
|
||||
* This also schedules an update to encourage updates happening on
|
||||
* unmetered networks like typical WiFi rather than networks that can
|
||||
* cost money or have caps. The logic for checking the state of the
|
||||
* internet connection is in {@link org.fdroid.fdroid.UpdateService#onHandleIntent(Intent)}
|
||||
* <p>
|
||||
* Some devices send multiple copies of given events, like a Moto G often
|
||||
* sends three {@code CONNECTED} events. So they have to be debounced to
|
||||
* keep the {@link #BROADCAST} useful.
|
||||
@ -92,6 +99,10 @@ public class WifiStateChangeService extends IntentService {
|
||||
wifiInfoThread = new WifiInfoThread();
|
||||
wifiInfoThread.start();
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT < 21 && wifiState == WifiManager.WIFI_STATE_ENABLED) {
|
||||
UpdateService.scheduleIfStillOnWifi(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user