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>
|
</receiver>
|
||||||
|
|
||||||
<service android:name=".UpdateService"/>
|
<service android:name=".UpdateService"/>
|
||||||
|
<service
|
||||||
|
android:name=".UpdateJobService"
|
||||||
|
android:exported="false"
|
||||||
|
android:permission="android.permission.BIND_JOB_SERVICE"/>
|
||||||
<service
|
<service
|
||||||
android:name=".net.DownloaderService"
|
android:name=".net.DownloaderService"
|
||||||
android:exported="false"/>
|
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_INCOMPAT_VERSIONS = false;
|
||||||
private static final boolean DEFAULT_SHOW_ROOT_APPS = true;
|
private static final boolean DEFAULT_SHOW_ROOT_APPS = true;
|
||||||
private static final boolean DEFAULT_SHOW_ANTI_FEATURE_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_PRIVILEGED_INSTALLER = true;
|
||||||
//private static final boolean DEFAULT_LOCAL_REPO_BONJOUR = true;
|
//private static final boolean DEFAULT_LOCAL_REPO_BONJOUR = true;
|
||||||
private static final long DEFAULT_KEEP_CACHE_TIME = TimeUnit.DAYS.toMillis(1);
|
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";
|
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
|
* 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.IntentService;
|
||||||
import android.app.NotificationManager;
|
import android.app.NotificationManager;
|
||||||
import android.app.PendingIntent;
|
import android.app.PendingIntent;
|
||||||
|
import android.app.job.JobInfo;
|
||||||
|
import android.app.job.JobScheduler;
|
||||||
import android.content.BroadcastReceiver;
|
import android.content.BroadcastReceiver;
|
||||||
|
import android.content.ComponentName;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.IntentFilter;
|
import android.content.IntentFilter;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
|
import android.os.AsyncTask;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
@ -51,6 +55,7 @@ import org.fdroid.fdroid.net.BluetoothDownloader;
|
|||||||
import org.fdroid.fdroid.net.ConnectivityMonitorService;
|
import org.fdroid.fdroid.net.ConnectivityMonitorService;
|
||||||
import org.fdroid.fdroid.views.main.MainActivity;
|
import org.fdroid.fdroid.views.main.MainActivity;
|
||||||
|
|
||||||
|
import java.lang.ref.WeakReference;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
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
|
* Schedule this service to update the app index while canceling any previously
|
||||||
* current preferences. Should be called a) at boot, b) if the preference
|
* scheduled updates, according to the current preferences. Should be called
|
||||||
* is changed, or c) on startup, in case we get upgraded.
|
* 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
|
if (Build.VERSION.SDK_INT < 21) {
|
||||||
.getDefaultSharedPreferences(ctx);
|
Intent intent = new Intent(context, UpdateService.class);
|
||||||
String sint = prefs.getString(Preferences.PREF_UPD_INTERVAL, "0");
|
PendingIntent pending = PendingIntent.getService(context, 0, intent, 0);
|
||||||
int interval = Integer.parseInt(sint);
|
|
||||||
|
|
||||||
Intent intent = new Intent(ctx, UpdateService.class);
|
AlarmManager alarm = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
|
||||||
PendingIntent pending = PendingIntent.getService(ctx, 0, intent, 0);
|
|
||||||
|
|
||||||
AlarmManager alarm = (AlarmManager) ctx
|
|
||||||
.getSystemService(Context.ALARM_SERVICE);
|
|
||||||
alarm.cancel(pending);
|
alarm.cancel(pending);
|
||||||
if (interval > 0) {
|
if (interval > 0) {
|
||||||
alarm.setInexactRepeating(AlarmManager.ELAPSED_REALTIME,
|
alarm.setInexactRepeating(AlarmManager.ELAPSED_REALTIME,
|
||||||
SystemClock.elapsedRealtime() + 5000,
|
SystemClock.elapsedRealtime() + 5000, interval, pending);
|
||||||
AlarmManager.INTERVAL_HOUR, pending);
|
|
||||||
Utils.debugLog(TAG, "Update scheduler alarm set");
|
Utils.debugLog(TAG, "Update scheduler alarm set");
|
||||||
} else {
|
} else {
|
||||||
Utils.debugLog(TAG, "Update scheduler alarm not set");
|
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;
|
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
|
@Override
|
||||||
public void onCreate() {
|
public void onCreate() {
|
||||||
super.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.
|
* 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 {
|
try {
|
||||||
|
final Preferences fdroidPrefs = Preferences.get();
|
||||||
// See if it's time to actually do anything yet...
|
// See if it's time to actually do anything yet...
|
||||||
int netState = ConnectivityMonitorService.getNetworkState(this);
|
int netState = ConnectivityMonitorService.getNetworkState(this);
|
||||||
if (address != null && address.startsWith(BluetoothDownloader.SCHEME)) {
|
if (address != null && address.startsWith(BluetoothDownloader.SCHEME)) {
|
||||||
@ -349,12 +380,9 @@ public class UpdateService extends IntentService {
|
|||||||
sendNoInternetToast();
|
sendNoInternetToast();
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
} else if (manualUpdate || forcedUpdate) {
|
||||||
|
|
||||||
final Preferences fdroidPrefs = Preferences.get();
|
|
||||||
if (manualUpdate || forcedUpdate) {
|
|
||||||
Utils.debugLog(TAG, "manually requested or forced update");
|
Utils.debugLog(TAG, "manually requested or forced update");
|
||||||
} else if (!verifyIsTimeForScheduledRun() || !fdroidPrefs.isBackgroundDownloadAllowed()) {
|
} else if (!fdroidPrefs.isBackgroundDownloadAllowed()) {
|
||||||
Utils.debugLog(TAG, "don't run update");
|
Utils.debugLog(TAG, "don't run update");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@ import android.net.DhcpInfo;
|
|||||||
import android.net.NetworkInfo;
|
import android.net.NetworkInfo;
|
||||||
import android.net.wifi.WifiInfo;
|
import android.net.wifi.WifiInfo;
|
||||||
import android.net.wifi.WifiManager;
|
import android.net.wifi.WifiManager;
|
||||||
|
import android.os.Build;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import android.support.v4.content.LocalBroadcastManager;
|
import android.support.v4.content.LocalBroadcastManager;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
@ -15,6 +16,7 @@ import android.util.Log;
|
|||||||
import org.apache.commons.net.util.SubnetUtils;
|
import org.apache.commons.net.util.SubnetUtils;
|
||||||
import org.fdroid.fdroid.FDroidApp;
|
import org.fdroid.fdroid.FDroidApp;
|
||||||
import org.fdroid.fdroid.Preferences;
|
import org.fdroid.fdroid.Preferences;
|
||||||
|
import org.fdroid.fdroid.UpdateService;
|
||||||
import org.fdroid.fdroid.Utils;
|
import org.fdroid.fdroid.Utils;
|
||||||
import org.fdroid.fdroid.data.Repo;
|
import org.fdroid.fdroid.data.Repo;
|
||||||
import org.fdroid.fdroid.localrepo.LocalRepoKeyStore;
|
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
|
* changed. Having the {@code Thread} also makes it easy to kill work
|
||||||
* that is in progress.
|
* that is in progress.
|
||||||
* <p>
|
* <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
|
* 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
|
* sends three {@code CONNECTED} events. So they have to be debounced to
|
||||||
* keep the {@link #BROADCAST} useful.
|
* keep the {@link #BROADCAST} useful.
|
||||||
@ -92,6 +99,10 @@ public class WifiStateChangeService extends IntentService {
|
|||||||
wifiInfoThread = new WifiInfoThread();
|
wifiInfoThread = new WifiInfoThread();
|
||||||
wifiInfoThread.start();
|
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