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