Merge 'Use_WorkManager' into 'master'

* origin/master:
  gitlab-ci: fix excluding @LargeTest from emulator jobs
  use TAG to identify CleanCacheWorker to WorkManager
  add WorkManagerTestRule to CleanCacheWorkerTest
  move static helper method into its class: CleanCacheWorker
  fdroidclient does not use variables for gradle dependencies
  Add WorkManagerTestRule.
  Use WorkManager to clean the cache.
  Add AndroidX WorkManager.

fdroid/fdroidclient!959
This commit is contained in:
Hans-Christoph Steiner 2021-01-27 16:27:40 +01:00
commit e6819e7f12
15 changed files with 441 additions and 335 deletions

View File

@ -82,7 +82,7 @@ errorprone:
- adb devices - adb devices
- adb shell input keyevent 82 & - adb shell input keyevent 82 &
- if [ $AVD_SDK -lt 25 ] || ! emulator -accel-check; then - if [ $AVD_SDK -lt 25 ] || ! emulator -accel-check; then
export FLAG=-Pandroid.testInstrumentationRunnerArguments.notAnnotation=android.support.test.filters.LargeTest; export FLAG=-Pandroid.testInstrumentationRunnerArguments.notAnnotation=androidx.test.filters.LargeTest;
fi fi
- ./gradlew connectedFullDebugAndroidTest $FLAG - ./gradlew connectedFullDebugAndroidTest $FLAG
|| ./gradlew connectedFullDebugAndroidTest $FLAG || ./gradlew connectedFullDebugAndroidTest $FLAG

View File

@ -145,6 +145,7 @@ dependencies {
implementation 'androidx.vectordrawable:vectordrawable:1.1.0' implementation 'androidx.vectordrawable:vectordrawable:1.1.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3' implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'androidx.palette:palette:1.0.0' implementation 'androidx.palette:palette:1.0.0'
implementation 'androidx.work:work-runtime:2.4.0'
implementation 'com.google.android.material:material:1.1.0' implementation 'com.google.android.material:material:1.1.0'
@ -176,6 +177,7 @@ dependencies {
testImplementation 'org.hamcrest:hamcrest:2.2' testImplementation 'org.hamcrest:hamcrest:2.2'
testImplementation 'org.bouncycastle:bcprov-jdk15on:1.65' testImplementation 'org.bouncycastle:bcprov-jdk15on:1.65'
androidTestImplementation 'androidx.arch.core:core-testing:2.1.0'
androidTestImplementation 'androidx.test:core:1.3.0' androidTestImplementation 'androidx.test:core:1.3.0'
androidTestImplementation 'androidx.test:runner:1.3.0' androidTestImplementation 'androidx.test:runner:1.3.0'
androidTestImplementation 'androidx.test:rules:1.3.0' androidTestImplementation 'androidx.test:rules:1.3.0'
@ -183,6 +185,7 @@ dependencies {
androidTestImplementation 'androidx.test.ext:junit:1.1.2' androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
androidTestImplementation 'androidx.test.uiautomator:uiautomator:2.2.0' androidTestImplementation 'androidx.test.uiautomator:uiautomator:2.2.0'
androidTestImplementation 'androidx.work:work-testing:2.4.0'
} }
checkstyle { checkstyle {

View File

@ -1,65 +0,0 @@
package org.fdroid.fdroid;
import android.app.Instrumentation;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.apache.commons.io.FileUtils;
import org.fdroid.fdroid.compat.FileCompatTest;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.io.File;
import java.io.IOException;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
@RunWith(AndroidJUnit4.class)
public class CleanCacheServiceTest {
public static final String TAG = "CleanCacheServiceTest";
@Test
public void testClearOldFiles() throws IOException, InterruptedException {
Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
File tempDir = FileCompatTest.getWriteableDir(instrumentation);
assertTrue(tempDir.isDirectory());
assertTrue(tempDir.canWrite());
File dir = new File(tempDir, "F-Droid-test.clearOldFiles");
FileUtils.deleteQuietly(dir);
assertTrue(dir.mkdirs());
assertTrue(dir.isDirectory());
File first = new File(dir, "first");
first.deleteOnExit();
File second = new File(dir, "second");
second.deleteOnExit();
assertFalse(first.exists());
assertFalse(second.exists());
assertTrue(first.createNewFile());
assertTrue(first.exists());
Thread.sleep(7000);
assertTrue(second.createNewFile());
assertTrue(second.exists());
CleanCacheService.clearOldFiles(dir, 3000); // check all in dir
assertFalse(first.exists());
assertTrue(second.exists());
Thread.sleep(7000);
CleanCacheService.clearOldFiles(second, 3000); // check just second file
assertFalse(first.exists());
assertFalse(second.exists());
// make sure it doesn't freak out on a non-existent file
File nonexistent = new File(tempDir, "nonexistent");
CleanCacheService.clearOldFiles(nonexistent, 1);
CleanCacheService.clearOldFiles(null, 1);
}
}

View File

@ -0,0 +1,89 @@
package org.fdroid.fdroid.work;
import android.app.Instrumentation;
import androidx.arch.core.executor.testing.InstantTaskExecutorRule;
import androidx.test.filters.LargeTest;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.work.OneTimeWorkRequest;
import androidx.work.WorkInfo;
import com.google.common.util.concurrent.ListenableFuture;
import org.apache.commons.io.FileUtils;
import org.fdroid.fdroid.compat.FileCompatTest;
import org.junit.Rule;
import org.junit.Test;
import java.io.File;
import java.io.IOException;
import java.util.concurrent.ExecutionException;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
/**
* This test cannot run on Robolectric unfortunately since it does not support
* <p>
* This is marked with {@link LargeTest} because it always fails on the emulator
* tests on GitLab CI. That excludes it from the test run there.
*/
@LargeTest
public class CleanCacheWorkerTest {
public static final String TAG = "CleanCacheWorkerEmulatorTest";
@Rule
public InstantTaskExecutorRule instantTaskExecutorRule = new InstantTaskExecutorRule();
@Rule
public WorkManagerTestRule workManagerTestRule = new WorkManagerTestRule();
@Test
public void testWorkRequest() throws ExecutionException, InterruptedException {
OneTimeWorkRequest request = new OneTimeWorkRequest.Builder(CleanCacheWorker.class).build();
workManagerTestRule.workManager.enqueue(request).getResult();
ListenableFuture<WorkInfo> workInfo = workManagerTestRule.workManager.getWorkInfoById(request.getId());
assertEquals(WorkInfo.State.SUCCEEDED, workInfo.get().getState());
}
@Test
public void testClearOldFiles() throws IOException, InterruptedException {
Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
File tempDir = FileCompatTest.getWriteableDir(instrumentation);
assertTrue(tempDir.isDirectory());
assertTrue(tempDir.canWrite());
File dir = new File(tempDir, "F-Droid-test.clearOldFiles");
FileUtils.deleteQuietly(dir);
assertTrue(dir.mkdirs());
assertTrue(dir.isDirectory());
File first = new File(dir, "first");
first.deleteOnExit();
File second = new File(dir, "second");
second.deleteOnExit();
assertFalse(first.exists());
assertFalse(second.exists());
assertTrue(first.createNewFile());
assertTrue(first.exists());
Thread.sleep(7000);
assertTrue(second.createNewFile());
assertTrue(second.exists());
CleanCacheWorker.clearOldFiles(dir, 3000); // check all in dir
assertFalse(first.exists());
assertTrue(second.exists());
Thread.sleep(7000);
CleanCacheWorker.clearOldFiles(second, 3000); // check just second file
assertFalse(first.exists());
assertFalse(second.exists());
// make sure it doesn't freak out on a non-existent file
File nonexistent = new File(tempDir, "nonexistent");
CleanCacheWorker.clearOldFiles(nonexistent, 1);
CleanCacheWorker.clearOldFiles(null, 1);
}
}

View File

@ -0,0 +1,33 @@
package org.fdroid.fdroid.work;
import android.app.Instrumentation;
import android.content.Context;
import android.util.Log;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.work.Configuration;
import androidx.work.WorkManager;
import androidx.work.testing.SynchronousExecutor;
import androidx.work.testing.WorkManagerTestInitHelper;
import org.junit.rules.TestWatcher;
import org.junit.runner.Description;
public class WorkManagerTestRule extends TestWatcher {
Context targetContext;
Context testContext;
Configuration configuration;
WorkManager workManager;
@Override
protected void starting(Description description) {
final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
targetContext = instrumentation.getTargetContext();
testContext = instrumentation.getContext();
configuration = new Configuration.Builder()
.setMinimumLoggingLevel(Log.DEBUG)
.setExecutor(new SynchronousExecutor())
.build();
WorkManagerTestInitHelper.initializeTestWorkManager(targetContext, configuration);
workManager = WorkManager.getInstance(targetContext);
}
}

View File

@ -241,14 +241,6 @@
android:name=".installer.InstallerService" android:name=".installer.InstallerService"
android:permission="android.permission.BIND_JOB_SERVICE" android:permission="android.permission.BIND_JOB_SERVICE"
android:exported="false"/> android:exported="false"/>
<service
android:name=".CleanCacheService"
android:permission="android.permission.BIND_JOB_SERVICE"
android:exported="false"/>
<service
android:name=".CleanCacheJobService"
android:permission="android.permission.BIND_JOB_SERVICE"
android:exported="false"/>
<service <service
android:name=".DeleteCacheService" android:name=".DeleteCacheService"
android:permission="android.permission.BIND_JOB_SERVICE" android:permission="android.permission.BIND_JOB_SERVICE"

View File

@ -1,22 +0,0 @@
package org.fdroid.fdroid;
import android.annotation.TargetApi;
import android.app.job.JobParameters;
import android.app.job.JobService;
/**
* Shim to run {@link CleanCacheService} with {@link android.app.job.JobScheduler}
*/
@TargetApi(21)
public class CleanCacheJobService extends JobService {
@Override
public boolean onStartJob(JobParameters jobParameters) {
CleanCacheService.start(this);
return false;
}
@Override
public boolean onStopJob(JobParameters jobParameters) {
return true;
}
}

View File

@ -1,193 +0,0 @@
package org.fdroid.fdroid;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.app.job.JobInfo;
import android.app.job.JobScheduler;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.Process;
import android.os.SystemClock;
import androidx.annotation.NonNull;
import androidx.core.app.JobIntentService;
import androidx.core.content.ContextCompat;
import org.apache.commons.io.FileUtils;
import org.fdroid.fdroid.installer.ApkCache;
import java.io.File;
import java.util.concurrent.TimeUnit;
/**
* Handles cleaning up caches files that are not going to be used, and do not
* block the operation of the app itself. For things that must happen before
* F-Droid starts normal operation, that should go into
* {@link FDroidApp#onCreate()}.
* <p>
* These files should only be deleted when they are at least an hour-ish old,
* in case they are actively in use while {@code CleanCacheService} is running.
* {@link #clearOldFiles(File, long)} checks the file age using access time from
* {@link android.system.StructStat#st_atime} on {@link android.os.Build.VERSION_CODES#LOLLIPOP}
* and newer. On older Android, last modified time from {@link File#lastModified()}
* is used.
*/
public class CleanCacheService extends JobIntentService {
public static final String TAG = "CleanCacheService";
private static final int JOB_ID = 0x982374;
/**
* 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.
*/
public static void schedule(Context context) {
long keepTime = Preferences.get().getKeepCacheTime();
long interval = TimeUnit.DAYS.toMillis(1);
if (keepTime < interval) {
interval = keepTime;
}
if (Build.VERSION.SDK_INT < 21) {
Intent intent = new Intent(context, CleanCacheService.class);
PendingIntent pending = PendingIntent.getService(context, 0, intent, 0);
AlarmManager alarm = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
alarm.cancel(pending);
alarm.setInexactRepeating(AlarmManager.ELAPSED_REALTIME,
SystemClock.elapsedRealtime() + 5000, interval, pending);
} else {
Utils.debugLog(TAG, "Using android-21 JobScheduler for updates");
JobScheduler jobScheduler = ContextCompat.getSystemService(context, JobScheduler.class);
ComponentName componentName = new ComponentName(context, CleanCacheJobService.class);
JobInfo.Builder builder = new JobInfo.Builder(JOB_ID, componentName)
.setRequiresDeviceIdle(true)
.setRequiresCharging(true)
.setPeriodic(interval);
if (Build.VERSION.SDK_INT >= 26) {
builder.setRequiresBatteryNotLow(true);
}
jobScheduler.schedule(builder.build());
}
}
public static void start(Context context) {
enqueueWork(context, CleanCacheService.class, JOB_ID, new Intent(context, CleanCacheService.class));
}
@Override
protected void onHandleWork(@NonNull Intent intent) {
Process.setThreadPriority(Process.THREAD_PRIORITY_LOWEST);
deleteExpiredApksFromCache();
deleteStrayIndexFiles();
deleteOldInstallerFiles();
deleteOldIcons();
}
/**
* All downloaded APKs will be cached for a certain amount of time, which is
* specified by the user in the "Keep Cache Time" preference. This removes
* any APK in the cache that is older than that preference specifies.
*/
private void deleteExpiredApksFromCache() {
File cacheDir = ApkCache.getApkCacheDir(getBaseContext());
clearOldFiles(cacheDir, Preferences.get().getKeepCacheTime());
}
/**
* {@link org.fdroid.fdroid.installer.Installer} instances copy the APK into
* a safe place before installing. It doesn't clean up them reliably yet.
*/
private void deleteOldInstallerFiles() {
File filesDir = getFilesDir();
if (filesDir == null) {
return;
}
final File[] files = filesDir.listFiles();
if (files == null) {
return;
}
for (File f : files) {
if (f.getName().endsWith(".apk")) {
clearOldFiles(f, TimeUnit.HOURS.toMillis(1));
}
}
}
/**
* Delete index files which were downloaded, but not removed (e.g. due to F-Droid being
* force closed during processing of the file, before getting a chance to delete). This
* may include both "index-*-downloaded" and "index-*-extracted.xml" files.
* <p>
* Note that if the SD card is not ready, then the cache directory will probably not be
* available. In this situation no files will be deleted (and thus they may still exist
* after the SD card becomes available).
* <p>
* This also deletes temp files that are created by
* {@link org.fdroid.fdroid.net.DownloaderFactory#create(Context, String)}, e.g. "dl-*"
*/
private void deleteStrayIndexFiles() {
File cacheDir = getCacheDir();
if (cacheDir == null) {
return;
}
final File[] files = cacheDir.listFiles();
if (files == null) {
return;
}
for (File f : files) {
if (f.getName().startsWith("index-")) {
clearOldFiles(f, TimeUnit.HOURS.toMillis(1));
}
if (f.getName().startsWith("dl-")) {
clearOldFiles(f, TimeUnit.HOURS.toMillis(1));
}
}
}
/**
* Delete cached icons that have not been accessed in over a year.
*/
private void deleteOldIcons() {
clearOldFiles(Utils.getImageCacheDir(this), TimeUnit.DAYS.toMillis(365));
}
/**
* Recursively delete files in {@code f} that were last used
* {@code millisAgo} milliseconds ago. On {@code android-21} and newer, this
* is based on the last access of the file, on older Android versions, it is
* based on the last time the file was modified, e.g. downloaded.
*
* @param f The file or directory to clean
* @param millisAgo The number of milliseconds old that marks a file for deletion.
*/
public static void clearOldFiles(File f, long millisAgo) {
if (f == null) {
return;
}
long olderThan = System.currentTimeMillis() - millisAgo;
if (f.isDirectory()) {
File[] files = f.listFiles();
if (files == null) {
return;
}
for (File file : files) {
clearOldFiles(file, millisAgo);
}
f.delete();
} else if (Build.VERSION.SDK_INT < 21) {
if (FileUtils.isFileOlder(f, olderThan)) {
f.delete();
}
} else {
CleanCacheService21.deleteIfOld(f, olderThan);
}
}
}

View File

@ -1,30 +0,0 @@
package org.fdroid.fdroid;
import android.system.ErrnoException;
import android.system.Os;
import android.system.StructStat;
import androidx.annotation.RequiresApi;
import java.io.File;
/**
* Helper class to prevent {@link VerifyError}s from occurring in {@link CleanCacheService#clearOldFiles(File, long)}
* due to the fact that {@link Os} was only introduced in API 21.
*/
@RequiresApi(21)
class CleanCacheService21 {
static void deleteIfOld(File file, long olderThan) {
if (file == null || !file.exists()) {
return;
}
try {
StructStat stat = Os.lstat(file.getAbsolutePath());
if ((stat.st_atime * 1000L) < olderThan) {
file.delete();
}
} catch (ErrnoException e) {
e.printStackTrace();
}
}
}

View File

@ -40,16 +40,17 @@ import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.os.Environment; import android.os.Environment;
import android.os.StrictMode; import android.os.StrictMode;
import androidx.annotation.Nullable;
import androidx.collection.LongSparseArray;
import androidx.core.content.ContextCompat;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Base64; import android.util.Base64;
import android.util.Log; import android.util.Log;
import android.view.Display; import android.view.Display;
import android.view.WindowManager; import android.view.WindowManager;
import android.widget.Toast; import android.widget.Toast;
import androidx.annotation.Nullable;
import androidx.collection.LongSparseArray;
import androidx.core.content.ContextCompat;
import com.nostra13.universalimageloader.cache.disc.DiskCache; import com.nostra13.universalimageloader.cache.disc.DiskCache;
import com.nostra13.universalimageloader.cache.disc.impl.UnlimitedDiskCache; import com.nostra13.universalimageloader.cache.disc.impl.UnlimitedDiskCache;
import com.nostra13.universalimageloader.cache.disc.impl.ext.LruDiskCache; import com.nostra13.universalimageloader.cache.disc.impl.ext.LruDiskCache;
@ -57,8 +58,7 @@ import com.nostra13.universalimageloader.core.DefaultConfigurationFactory;
import com.nostra13.universalimageloader.core.ImageLoader; import com.nostra13.universalimageloader.core.ImageLoader;
import com.nostra13.universalimageloader.core.ImageLoaderConfiguration; import com.nostra13.universalimageloader.core.ImageLoaderConfiguration;
import com.nostra13.universalimageloader.core.process.BitmapProcessor; import com.nostra13.universalimageloader.core.process.BitmapProcessor;
import info.guardianproject.netcipher.NetCipher;
import info.guardianproject.netcipher.proxy.OrbotHelper;
import org.acra.ACRA; import org.acra.ACRA;
import org.acra.ReportField; import org.acra.ReportField;
import org.acra.ReportingInteractionMode; import org.acra.ReportingInteractionMode;
@ -73,20 +73,25 @@ import org.fdroid.fdroid.data.Repo;
import org.fdroid.fdroid.installer.ApkFileProvider; import org.fdroid.fdroid.installer.ApkFileProvider;
import org.fdroid.fdroid.installer.InstallHistoryService; import org.fdroid.fdroid.installer.InstallHistoryService;
import org.fdroid.fdroid.nearby.SDCardScannerService; import org.fdroid.fdroid.nearby.SDCardScannerService;
import org.fdroid.fdroid.nearby.WifiStateChangeService;
import org.fdroid.fdroid.net.ConnectivityMonitorService; import org.fdroid.fdroid.net.ConnectivityMonitorService;
import org.fdroid.fdroid.net.Downloader; import org.fdroid.fdroid.net.Downloader;
import org.fdroid.fdroid.net.HttpDownloader; import org.fdroid.fdroid.net.HttpDownloader;
import org.fdroid.fdroid.net.ImageLoaderForUIL; import org.fdroid.fdroid.net.ImageLoaderForUIL;
import org.fdroid.fdroid.nearby.WifiStateChangeService;
import org.fdroid.fdroid.panic.HidingManager; import org.fdroid.fdroid.panic.HidingManager;
import org.fdroid.fdroid.work.CleanCacheWorker;
import javax.microedition.khronos.opengles.GL10;
import java.io.IOException; import java.io.IOException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.security.Security; import java.security.Security;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
import javax.microedition.khronos.opengles.GL10;
import info.guardianproject.netcipher.NetCipher;
import info.guardianproject.netcipher.proxy.OrbotHelper;
@ReportsCrashes(mailTo = BuildConfig.ACRA_REPORT_EMAIL, @ReportsCrashes(mailTo = BuildConfig.ACRA_REPORT_EMAIL,
mode = ReportingInteractionMode.DIALOG, mode = ReportingInteractionMode.DIALOG,
reportDialogClass = org.fdroid.fdroid.acra.CrashReportActivity.class, reportDialogClass = org.fdroid.fdroid.acra.CrashReportActivity.class,
@ -421,7 +426,7 @@ public class FDroidApp extends Application {
} }
}); });
CleanCacheService.schedule(this); CleanCacheWorker.schedule(this);
notificationHelper = new NotificationHelper(getApplicationContext()); notificationHelper = new NotificationHelper(getApplicationContext());
@ -551,7 +556,7 @@ public class FDroidApp extends Application {
* problems that arise from executing the code twice. This happens due to the `android:process` * problems that arise from executing the code twice. This happens due to the `android:process`
* statement in AndroidManifest.xml causes another process to be created to run * statement in AndroidManifest.xml causes another process to be created to run
* {@link org.fdroid.fdroid.acra.CrashReportActivity}. This was causing lots of things to be * {@link org.fdroid.fdroid.acra.CrashReportActivity}. This was causing lots of things to be
* started/run twice including {@link CleanCacheService} and {@link WifiStateChangeService}. * started/run twice including {@link CleanCacheWorker} and {@link WifiStateChangeService}.
* <p> * <p>
* Note that it is not perfect, because some devices seem to not provide a list of running app * Note that it is not perfect, because some devices seem to not provide a list of running app
* processes when asked. In such situations, F-Droid may regress to the behaviour where some * processes when asked. In such situations, F-Droid may regress to the behaviour where some

View File

@ -3,9 +3,10 @@ package org.fdroid.fdroid.receiver;
import android.content.BroadcastReceiver; import android.content.BroadcastReceiver;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import org.fdroid.fdroid.CleanCacheService;
import org.fdroid.fdroid.DeleteCacheService; import org.fdroid.fdroid.DeleteCacheService;
import org.fdroid.fdroid.Utils; import org.fdroid.fdroid.Utils;
import org.fdroid.fdroid.work.CleanCacheWorker;
public class DeviceStorageReceiver extends BroadcastReceiver { public class DeviceStorageReceiver extends BroadcastReceiver {
@Override @Override
@ -18,7 +19,7 @@ public class DeviceStorageReceiver extends BroadcastReceiver {
int percentageFree = Utils.getPercent(Utils.getImageCacheDirAvailableMemory(context), int percentageFree = Utils.getPercent(Utils.getImageCacheDirAvailableMemory(context),
Utils.getImageCacheDirTotalMemory(context)); Utils.getImageCacheDirTotalMemory(context));
if (percentageFree > 2) { if (percentageFree > 2) {
CleanCacheService.start(context); CleanCacheWorker.schedule(context);
} else { } else {
DeleteCacheService.deleteAll(context); DeleteCacheService.deleteAll(context);
} }

View File

@ -48,7 +48,6 @@ import androidx.preference.SwitchPreference;
import androidx.recyclerview.widget.LinearSmoothScroller; import androidx.recyclerview.widget.LinearSmoothScroller;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import org.fdroid.fdroid.CleanCacheService;
import org.fdroid.fdroid.FDroidApp; import org.fdroid.fdroid.FDroidApp;
import org.fdroid.fdroid.Languages; import org.fdroid.fdroid.Languages;
import org.fdroid.fdroid.Preferences; import org.fdroid.fdroid.Preferences;
@ -58,6 +57,7 @@ import org.fdroid.fdroid.Utils;
import org.fdroid.fdroid.data.RepoProvider; import org.fdroid.fdroid.data.RepoProvider;
import org.fdroid.fdroid.installer.InstallHistoryService; import org.fdroid.fdroid.installer.InstallHistoryService;
import org.fdroid.fdroid.installer.PrivilegedInstaller; import org.fdroid.fdroid.installer.PrivilegedInstaller;
import org.fdroid.fdroid.work.CleanCacheWorker;
import info.guardianproject.netcipher.proxy.OrbotHelper; import info.guardianproject.netcipher.proxy.OrbotHelper;
@ -304,7 +304,7 @@ public class PreferencesFragment extends PreferenceFragmentCompat
entrySummary(key); entrySummary(key);
if (changing if (changing
&& currentKeepCacheTime != Preferences.get().getKeepCacheTime()) { && currentKeepCacheTime != Preferences.get().getKeepCacheTime()) {
CleanCacheService.schedule(getActivity()); CleanCacheWorker.schedule(requireContext());
} }
break; break;

View File

@ -0,0 +1,215 @@
package org.fdroid.fdroid.work;
import android.content.Context;
import android.os.Build;
import android.os.Process;
import android.system.ErrnoException;
import android.system.Os;
import android.system.StructStat;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import androidx.work.Constraints;
import androidx.work.ExistingPeriodicWorkPolicy;
import androidx.work.PeriodicWorkRequest;
import androidx.work.WorkManager;
import androidx.work.Worker;
import androidx.work.WorkerParameters;
import org.apache.commons.io.FileUtils;
import org.fdroid.fdroid.Preferences;
import org.fdroid.fdroid.Utils;
import org.fdroid.fdroid.installer.ApkCache;
import java.io.File;
import java.util.concurrent.TimeUnit;
public class CleanCacheWorker extends Worker {
public static final String TAG = "CleanCacheWorker";
public CleanCacheWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) {
super(context, workerParams);
}
/**
* Schedule or cancel a work request 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.
*/
public static void schedule(@NonNull final Context context) {
final WorkManager workManager = WorkManager.getInstance(context);
final long keepTime = Preferences.get().getKeepCacheTime();
long interval = TimeUnit.DAYS.toMillis(1);
if (keepTime < interval) {
interval = keepTime;
}
final Constraints.Builder constraintsBuilder = new Constraints.Builder()
.setRequiresCharging(true)
.setRequiresBatteryNotLow(true);
if (Build.VERSION.SDK_INT >= 23) {
constraintsBuilder.setRequiresDeviceIdle(true);
}
final PeriodicWorkRequest cleanCache =
new PeriodicWorkRequest.Builder(CleanCacheWorker.class, interval, TimeUnit.MILLISECONDS)
.setConstraints(constraintsBuilder.build())
.build();
workManager.enqueueUniquePeriodicWork(TAG, ExistingPeriodicWorkPolicy.REPLACE, cleanCache);
Utils.debugLog(TAG, "Scheduled periodic work for cleaning the cache.");
}
@NonNull
@Override
public Result doWork() {
Process.setThreadPriority(Process.THREAD_PRIORITY_LOWEST);
try {
deleteExpiredApksFromCache();
deleteStrayIndexFiles();
deleteOldInstallerFiles();
deleteOldIcons();
return Result.success();
} catch (Exception e) {
return Result.failure();
}
}
/**
* All downloaded APKs will be cached for a certain amount of time, which is
* specified by the user in the "Keep Cache Time" preference. This removes
* any APK in the cache that is older than that preference specifies.
*/
private void deleteExpiredApksFromCache() {
File cacheDir = ApkCache.getApkCacheDir(getApplicationContext());
clearOldFiles(cacheDir, Preferences.get().getKeepCacheTime());
}
/**
* {@link org.fdroid.fdroid.installer.Installer} instances copy the APK into
* a safe place before installing. It doesn't clean up them reliably yet.
*/
private void deleteOldInstallerFiles() {
File filesDir = getApplicationContext().getFilesDir();
if (filesDir == null) {
Utils.debugLog(TAG, "The files directory doesn't exist.");
return;
}
final File[] files = filesDir.listFiles();
if (files == null) {
Utils.debugLog(TAG, "The files directory doesn't have any files.");
return;
}
for (File f : files) {
if (f.getName().endsWith(".apk")) {
clearOldFiles(f, TimeUnit.HOURS.toMillis(1));
}
}
}
/**
* Delete index files which were downloaded, but not removed (e.g. due to F-Droid being
* force closed during processing of the file, before getting a chance to delete). This
* may include both "index-*-downloaded" and "index-*-extracted.xml" files.
* <p>
* Note that if the SD card is not ready, then the cache directory will probably not be
* available. In this situation no files will be deleted (and thus they may still exist
* after the SD card becomes available).
* <p>
* This also deletes temp files that are created by
* {@link org.fdroid.fdroid.net.DownloaderFactory#create(Context, String)}, e.g. "dl-*"
*/
private void deleteStrayIndexFiles() {
File cacheDir = getApplicationContext().getCacheDir();
if (cacheDir == null) {
Utils.debugLog(TAG, "The cache directory doesn't exist.");
return;
}
final File[] files = cacheDir.listFiles();
if (files == null) {
Utils.debugLog(TAG, "The cache directory doesn't have files.");
return;
}
for (File f : files) {
if (f.getName().startsWith("index-")) {
clearOldFiles(f, TimeUnit.HOURS.toMillis(1));
}
if (f.getName().startsWith("dl-")) {
clearOldFiles(f, TimeUnit.HOURS.toMillis(1));
}
}
}
/**
* Delete cached icons that have not been accessed in over a year.
*/
private void deleteOldIcons() {
clearOldFiles(Utils.getImageCacheDir(getApplicationContext()), TimeUnit.DAYS.toMillis(365));
}
/**
* Recursively delete files in {@code f} that were last used
* {@code millisAgo} milliseconds ago. On {@code android-21} and newer, this
* is based on the last access of the file, on older Android versions, it is
* based on the last time the file was modified, e.g. downloaded.
*
* @param f The file or directory to clean
* @param millisAgo The number of milliseconds old that marks a file for deletion.
*/
public static void clearOldFiles(File f, long millisAgo) {
if (f == null) {
Utils.debugLog(TAG, "No files to be cleared.");
return;
}
long olderThan = System.currentTimeMillis() - millisAgo;
if (f.isDirectory()) {
File[] files = f.listFiles();
if (files == null) {
Utils.debugLog(TAG, "No more files to be cleared.");
return;
}
for (File file : files) {
clearOldFiles(file, millisAgo);
}
deleteFileAndLog(f);
} else if (Build.VERSION.SDK_INT <= 21) {
if (FileUtils.isFileOlder(f, olderThan)) {
deleteFileAndLog(f);
}
} else {
Impl21.deleteIfOld(f, olderThan);
}
}
private static void deleteFileAndLog(final File file) {
file.delete();
Utils.debugLog(TAG, "Deleted file: " + file);
}
@RequiresApi(api = 21)
private static class Impl21 {
/**
* Recursively delete files in {@code f} that were last used
* {@code millisAgo} milliseconds ago. On {@code android-21} and newer, this
* is based on the last access of the file, on older Android versions, it is
* based on the last time the file was modified, e.g. downloaded.
*
* @param file The file or directory to clean
* @param olderThan The number of milliseconds old that marks a file for deletion.
*/
public static void deleteIfOld(File file, long olderThan) {
if (file == null || !file.exists()) {
Utils.debugLog(TAG, "No files to be cleared.");
return;
}
try {
StructStat stat = Os.lstat(file.getAbsolutePath());
if ((stat.st_atime * 1000L) < olderThan) {
deleteFileAndLog(file);
}
} catch (ErrnoException e) {
Utils.debugLog(TAG, "An exception occurred while deleting: ", e);
}
}
}
}

Binary file not shown.

View File

@ -63,6 +63,7 @@
<trusted-key id="7f36e793ae3252e5d9e9b98fee9e7dc9d92fc896" group="com.google.errorprone"/> <trusted-key id="7f36e793ae3252e5d9e9b98fee9e7dc9d92fc896" group="com.google.errorprone"/>
<trusted-key id="7faa0f2206de228f0db01ad741321490758aad6f" group="org.codehaus.groovy" name="groovy-all" version="2.4.15"/> <trusted-key id="7faa0f2206de228f0db01ad741321490758aad6f" group="org.codehaus.groovy" name="groovy-all" version="2.4.15"/>
<trusted-key id="8254180bfc943b816e0b5e2e5e2f2b3d474efe6b" group="it.unimi.dsi" name="fastutil" version="7.2.0"/> <trusted-key id="8254180bfc943b816e0b5e2e5e2f2b3d474efe6b" group="it.unimi.dsi" name="fastutil" version="7.2.0"/>
<trusted-key id="8756c4f765c9ac3cb6b85d62379ce192d401ab61" group="org.jetbrains.kotlinx"/>
<trusted-key id="90ee19787a7bcf6fd37a1e9180c08b1c29100955"> <trusted-key id="90ee19787a7bcf6fd37a1e9180c08b1c29100955">
<trusting group="com.squareup" name="javawriter"/> <trusting group="com.squareup" name="javawriter"/>
<trusting group="com.squareup" name="javawriter" version="2.1.1"/> <trusting group="com.squareup" name="javawriter" version="2.1.1"/>
@ -79,11 +80,17 @@
<trusted-key id="afcc4c7594d09e2182c60e0f7a01b0f236e5430f" group="com.google.code.gson"/> <trusted-key id="afcc4c7594d09e2182c60e0f7a01b0f236e5430f" group="com.google.code.gson"/>
<trusted-key id="b6e73d84ea4fcc47166087253faad2cd5ecbb314" group="org.apache.commons"/> <trusted-key id="b6e73d84ea4fcc47166087253faad2cd5ecbb314" group="org.apache.commons"/>
<trusted-key id="b801e2f8ef035068ec1139cc29579f18fa8fd93b" group="com.google.j2objc" name="j2objc-annotations" version="1.1"/> <trusted-key id="b801e2f8ef035068ec1139cc29579f18fa8fd93b" group="com.google.j2objc" name="j2objc-annotations" version="1.1"/>
<trusted-key id="bdb5fa4fe719d787fb3d3197f6d4a1d411e9d1ae" group="com.google.guava" name="listenablefuture" version="9999.0-empty-to-avoid-conflict-with-guava"/> <trusted-key id="bdb5fa4fe719d787fb3d3197f6d4a1d411e9d1ae">
<trusting group="com.google.guava" name="listenablefuture" version="9999.0-empty-to-avoid-conflict-with-guava"/>
<trusting group="com.google.guava" name="listenablefuture"/>
</trusted-key>
<trusted-key id="c7be5bcc9fec15518cfda882b0f3710fa64900e7" group="com.google.auto.value"/> <trusted-key id="c7be5bcc9fec15518cfda882b0f3710fa64900e7" group="com.google.auto.value"/>
<trusted-key id="cacfbd4755a2fc78709bdd92be096e29edb8d141" group="net.sf.proguard"/> <trusted-key id="cacfbd4755a2fc78709bdd92be096e29edb8d141" group="net.sf.proguard"/>
<trusted-key id="cb3190ca7842439e57f3712e44ce7bf2825ea2cd" group="com.ibm.icu" name="icu4j" version="53.1"/> <trusted-key id="cb3190ca7842439e57f3712e44ce7bf2825ea2cd" group="com.ibm.icu" name="icu4j" version="53.1"/>
<trusted-key id="cc4483cd6a3eb2939b948667a1b4460d8ba7b9af" group="org.mockito" name="mockito-core" version="3.3.3"/> <trusted-key id="cc4483cd6a3eb2939b948667a1b4460d8ba7b9af">
<trusting group="org.mockito" name="mockito-core" version="3.3.3"/>
<trusting group="org.mockito" name="mockito-core"/>
</trusted-key>
<trusted-key id="cd5464315f0b98c77e6e8ecd9daadc1c9fcc82d0"> <trusted-key id="cd5464315f0b98c77e6e8ecd9daadc1c9fcc82d0">
<trusting group="commons-io"/> <trusting group="commons-io"/>
<trusting group="commons-cli"/> <trusting group="commons-cli"/>
@ -163,6 +170,11 @@
<sha256 value="c0c4ed0160cd2ca18390015de8b392b697c173327c6b2c0947d4e62f6958c05d" origin="Generated by Gradle because artifact wasn't signed"/> <sha256 value="c0c4ed0160cd2ca18390015de8b392b697c173327c6b2c0947d4e62f6958c05d" origin="Generated by Gradle because artifact wasn't signed"/>
</artifact> </artifact>
</component> </component>
<component group="androidx.arch.core" name="core-testing" version="2.1.0">
<artifact name="core-testing-2.1.0.aar">
<sha256 value="c57ffade2a9a844bd62b4f4c3916fad5e09e9f24cceba27e932c25bb7a6d1d8f" origin="Generated by Gradle because artifact wasn't signed"/>
</artifact>
</component>
<component group="androidx.asynclayoutinflater" name="asynclayoutinflater" version="1.0.0"> <component group="androidx.asynclayoutinflater" name="asynclayoutinflater" version="1.0.0">
<artifact name="asynclayoutinflater-1.0.0.aar"> <artifact name="asynclayoutinflater-1.0.0.aar">
<sha256 value="f7eab60c57addd94bb06275832fe7600611beaaae1a1ec597c231956faf96c8b" origin="Generated by Gradle because artifact wasn't signed"/> <sha256 value="f7eab60c57addd94bb06275832fe7600611beaaae1a1ec597c231956faf96c8b" origin="Generated by Gradle because artifact wasn't signed"/>
@ -346,6 +358,11 @@
<sha256 value="a84842ffc0f14e518db75c05cc112680a8a4a164fa78395be32d88304a439423" origin="Generated by Gradle because artifact wasn't signed"/> <sha256 value="a84842ffc0f14e518db75c05cc112680a8a4a164fa78395be32d88304a439423" origin="Generated by Gradle because artifact wasn't signed"/>
</artifact> </artifact>
</component> </component>
<component group="androidx.lifecycle" name="lifecycle-livedata" version="2.1.0">
<artifact name="lifecycle-livedata-2.1.0.aar">
<sha256 value="242e446bed3db36f0df0aab0cb7f91060bd2dab7adcad1117adf54e724cd1d26" origin="Generated by Gradle because artifact wasn't signed"/>
</artifact>
</component>
<component group="androidx.lifecycle" name="lifecycle-livedata-core" version="2.2.0"> <component group="androidx.lifecycle" name="lifecycle-livedata-core" version="2.2.0">
<artifact name="lifecycle-livedata-core-2.2.0.aar"> <artifact name="lifecycle-livedata-core-2.2.0.aar">
<sha256 value="556c1f3af90aa9d7d0d330565adbf6da71b2429148bac91e07c485f4f9abf614" origin="Generated by Gradle because artifact wasn't signed"/> <sha256 value="556c1f3af90aa9d7d0d330565adbf6da71b2429148bac91e07c485f4f9abf614" origin="Generated by Gradle because artifact wasn't signed"/>
@ -367,6 +384,11 @@
<sha256 value="7f154066fed1c9162870f728b208352831b3d1f8f23f5d388958a50ca4e9f441" origin="Generated by Gradle because artifact wasn't signed"/> <sha256 value="7f154066fed1c9162870f728b208352831b3d1f8f23f5d388958a50ca4e9f441" origin="Generated by Gradle because artifact wasn't signed"/>
</artifact> </artifact>
</component> </component>
<component group="androidx.lifecycle" name="lifecycle-service" version="2.1.0">
<artifact name="lifecycle-service-2.1.0.aar">
<sha256 value="23516745f34f16ff7850bb1eadd55cf193dd789cba428de4bca120433e3bfd69" origin="Generated by Gradle because artifact wasn't signed"/>
</artifact>
</component>
<component group="androidx.lifecycle" name="lifecycle-viewmodel" version="2.2.0"> <component group="androidx.lifecycle" name="lifecycle-viewmodel" version="2.2.0">
<artifact name="lifecycle-viewmodel-2.2.0.aar"> <artifact name="lifecycle-viewmodel-2.2.0.aar">
<sha256 value="967efab24d6c49dd414a8c0ac4a1cd09b018f0b8bb43b739ad360c4158ebde27" origin="Generated by Gradle because artifact wasn't signed"/> <sha256 value="967efab24d6c49dd414a8c0ac4a1cd09b018f0b8bb43b739ad360c4158ebde27" origin="Generated by Gradle because artifact wasn't signed"/>
@ -439,6 +461,16 @@
<sha256 value="fe321062a6e4e168b9c2b39a1137564c8784a2e5849402729768d5d7c4d972ef" origin="Generated by Gradle because artifact wasn't signed"/> <sha256 value="fe321062a6e4e168b9c2b39a1137564c8784a2e5849402729768d5d7c4d972ef" origin="Generated by Gradle because artifact wasn't signed"/>
</artifact> </artifact>
</component> </component>
<component group="androidx.room" name="room-common" version="2.2.5">
<artifact name="room-common-2.2.5.jar">
<sha256 value="2b130dd4a1d3d91b6701ed33096d389f01c4fc1197a7acd6b91724ddc5acfc06" origin="Generated by Gradle because artifact wasn't signed"/>
</artifact>
</component>
<component group="androidx.room" name="room-runtime" version="2.2.5">
<artifact name="room-runtime-2.2.5.aar">
<sha256 value="24a5549b796e43e337513d2908adac67f45350d9a90bca7e2e6120692140bb14" origin="Generated by Gradle because artifact wasn't signed"/>
</artifact>
</component>
<component group="androidx.savedstate" name="savedstate" version="1.0.0"> <component group="androidx.savedstate" name="savedstate" version="1.0.0">
<artifact name="savedstate-1.0.0.aar"> <artifact name="savedstate-1.0.0.aar">
<sha256 value="2510a5619c37579c9ce1a04574faaf323cd0ffe2fc4e20fa8f8f01e5bb402e83" origin="Generated by Gradle because artifact wasn't signed"/> <sha256 value="2510a5619c37579c9ce1a04574faaf323cd0ffe2fc4e20fa8f8f01e5bb402e83" origin="Generated by Gradle because artifact wasn't signed"/>
@ -455,6 +487,16 @@
<sha256 value="40e90f96838c2a8156ab51b181400767049f387cec8c695e412d3d9205b0745b" origin="Generated by Gradle because artifact wasn't signed"/> <sha256 value="40e90f96838c2a8156ab51b181400767049f387cec8c695e412d3d9205b0745b" origin="Generated by Gradle because artifact wasn't signed"/>
</artifact> </artifact>
</component> </component>
<component group="androidx.sqlite" name="sqlite" version="2.1.0">
<artifact name="sqlite-2.1.0.aar">
<sha256 value="8341ff092d6060d62a07227f29237155fff36fb16f96c95fbd9a884e375db912" origin="Generated by Gradle because artifact wasn't signed"/>
</artifact>
</component>
<component group="androidx.sqlite" name="sqlite-framework" version="2.1.0">
<artifact name="sqlite-framework-2.1.0.aar">
<sha256 value="8673737fdb2efbad91aeaeed1927ebb29212d36a867d93b9639c8069019f8a1e" origin="Generated by Gradle because artifact wasn't signed"/>
</artifact>
</component>
<component group="androidx.swiperefreshlayout" name="swiperefreshlayout" version="1.0.0"> <component group="androidx.swiperefreshlayout" name="swiperefreshlayout" version="1.0.0">
<artifact name="swiperefreshlayout-1.0.0.aar"> <artifact name="swiperefreshlayout-1.0.0.aar">
<sha256 value="9761b3a809c9b093fd06a3c4bbc645756dec0e95b5c9da419bc9f2a3f3026e8d" origin="Generated by Gradle because artifact wasn't signed"/> <sha256 value="9761b3a809c9b093fd06a3c4bbc645756dec0e95b5c9da419bc9f2a3f3026e8d" origin="Generated by Gradle because artifact wasn't signed"/>
@ -610,6 +652,21 @@
<sha256 value="4063bca7fe94fe65c98f4168f97ae10e02da0248598ad3ac21c432c7f608a17a" origin="Generated by Gradle because artifact wasn't signed"/> <sha256 value="4063bca7fe94fe65c98f4168f97ae10e02da0248598ad3ac21c432c7f608a17a" origin="Generated by Gradle because artifact wasn't signed"/>
</artifact> </artifact>
</component> </component>
<component group="androidx.work" name="work-runtime" version="2.4.0">
<artifact name="work-runtime-2.4.0.aar">
<sha256 value="7801441cb973fb007ef311d3db35c3b3c9d011cef890357e7ca33890833354e5" origin="Generated by Gradle because artifact wasn't signed"/>
</artifact>
</component>
<component group="androidx.work" name="work-runtime-ktx" version="2.4.0">
<artifact name="work-runtime-ktx-2.4.0.aar">
<sha256 value="290d2e9c1a22388b3f0c3dbc204a6176f8c49174126de884820befa83899f129" origin="Generated by Gradle because artifact wasn't signed"/>
</artifact>
</component>
<component group="androidx.work" name="work-testing" version="2.4.0">
<artifact name="work-testing-2.4.0.aar">
<sha256 value="dc1d9f743c59ee08c605aa924fc20819b6a0b734338e0a95af1943c3cd806e23" origin="Generated by Gradle because artifact wasn't signed"/>
</artifact>
</component>
<component group="antlr" name="antlr" version="2.7.7"> <component group="antlr" name="antlr" version="2.7.7">
<artifact name="antlr-2.7.7.jar"> <artifact name="antlr-2.7.7.jar">
<sha256 value="88fbda4b912596b9f56e8e12e580cc954bacfb51776ecfddd3e18fc1cf56dc4c" origin="Generated by Gradle because artifact wasn't signed"/> <sha256 value="88fbda4b912596b9f56e8e12e580cc954bacfb51776ecfddd3e18fc1cf56dc4c" origin="Generated by Gradle because artifact wasn't signed"/>
@ -768,6 +825,9 @@
<artifact name="aapt2-3.6.3-6040484-linux.jar"> <artifact name="aapt2-3.6.3-6040484-linux.jar">
<sha256 value="1e2fcdbe75c2c74c6f9ae841e032fd1c803d30f59505d657b3dbcec1cecf961b" origin="Generated by Gradle because artifact wasn't signed"/> <sha256 value="1e2fcdbe75c2c74c6f9ae841e032fd1c803d30f59505d657b3dbcec1cecf961b" origin="Generated by Gradle because artifact wasn't signed"/>
</artifact> </artifact>
<artifact name="aapt2-3.6.3-6040484-windows.jar">
<sha256 value="82ea931921dede9d6a5e6a6735c99b4f289fd9e6951da54453c3b32c3684d82a" origin="Generated by Gradle because artifact wasn't signed"/>
</artifact>
<artifact name="aapt2-3.6.3-6040484.pom"> <artifact name="aapt2-3.6.3-6040484.pom">
<sha256 value="cc6e7d9c35677382afdbce47d5e81e683bee37a9af8a595fcab3b8948f7d3d51" origin="Generated by Gradle because artifact wasn't signed"/> <sha256 value="cc6e7d9c35677382afdbce47d5e81e683bee37a9af8a595fcab3b8948f7d3d51" origin="Generated by Gradle because artifact wasn't signed"/>
</artifact> </artifact>
@ -854,6 +914,7 @@
</component> </component>
<component group="com.android.tools.build" name="transform-api" version="2.0.0-deprecated-use-gradle-api"> <component group="com.android.tools.build" name="transform-api" version="2.0.0-deprecated-use-gradle-api">
<artifact name="transform-api-2.0.0-deprecated-use-gradle-api.jar"> <artifact name="transform-api-2.0.0-deprecated-use-gradle-api.jar">
<pgp value="3872ed7d5904493d23d78fa2c4c8cb73b1435348"/>
<sha256 value="e8b4151ae1679f1abe7a14ee371ac9b3c651ae7b63290d1f586bdd0f78face9a" origin="Generated by Gradle"/> <sha256 value="e8b4151ae1679f1abe7a14ee371ac9b3c651ae7b63290d1f586bdd0f78face9a" origin="Generated by Gradle"/>
</artifact> </artifact>
<artifact name="transform-api-2.0.0-deprecated-use-gradle-api.pom"> <artifact name="transform-api-2.0.0-deprecated-use-gradle-api.pom">
@ -1143,6 +1204,7 @@
</component> </component>
<component group="com.google.code.findbugs" name="jsr305" version="3.0.2"> <component group="com.google.code.findbugs" name="jsr305" version="3.0.2">
<artifact name="jsr305-3.0.2.jar"> <artifact name="jsr305-3.0.2.jar">
<pgp value="7616eb882daf57a11477aaf559a252fb1199d873"/>
<sha256 value="766ad2a0783f2687962c8ad74ceecc38a28b9f72a2d085ee438b7813e928d0c7" origin="Generated by Gradle"/> <sha256 value="766ad2a0783f2687962c8ad74ceecc38a28b9f72a2d085ee438b7813e928d0c7" origin="Generated by Gradle"/>
</artifact> </artifact>
<artifact name="jsr305-3.0.2.pom"> <artifact name="jsr305-3.0.2.pom">
@ -1173,6 +1235,7 @@
</component> </component>
<component group="com.google.errorprone" name="error_prone_annotations" version="2.2.0"> <component group="com.google.errorprone" name="error_prone_annotations" version="2.2.0">
<artifact name="error_prone_annotations-2.2.0.jar"> <artifact name="error_prone_annotations-2.2.0.jar">
<pgp value="e77417ac194160a3fabd04969a259c7ee636c5ed"/>
<sha256 value="6ebd22ca1b9d8ec06d41de8d64e0596981d9607b42035f9ed374f9de271a481a" origin="Generated by Gradle"/> <sha256 value="6ebd22ca1b9d8ec06d41de8d64e0596981d9607b42035f9ed374f9de271a481a" origin="Generated by Gradle"/>
</artifact> </artifact>
<artifact name="error_prone_annotations-2.2.0.pom"> <artifact name="error_prone_annotations-2.2.0.pom">
@ -1181,6 +1244,7 @@
</component> </component>
<component group="com.google.errorprone" name="error_prone_annotations" version="2.3.1"> <component group="com.google.errorprone" name="error_prone_annotations" version="2.3.1">
<artifact name="error_prone_annotations-2.3.1.jar"> <artifact name="error_prone_annotations-2.3.1.jar">
<pgp value="7f36e793ae3252e5d9e9b98fee9e7dc9d92fc896"/>
<sha256 value="10a5949aa0f95c8de4fd47edfe20534d2acefd8c224f8afea1f607e112816120" origin="Generated by Gradle"/> <sha256 value="10a5949aa0f95c8de4fd47edfe20534d2acefd8c224f8afea1f607e112816120" origin="Generated by Gradle"/>
</artifact> </artifact>
<artifact name="error_prone_annotations-2.3.1.pom"> <artifact name="error_prone_annotations-2.3.1.pom">
@ -1306,6 +1370,7 @@
</component> </component>
<component group="com.google.j2objc" name="j2objc-annotations" version="1.1"> <component group="com.google.j2objc" name="j2objc-annotations" version="1.1">
<artifact name="j2objc-annotations-1.1.jar"> <artifact name="j2objc-annotations-1.1.jar">
<pgp value="b801e2f8ef035068ec1139cc29579f18fa8fd93b"/>
<sha256 value="2994a7eb78f2710bd3d3bfb639b2c94e219cedac0d4d084d516e78c16dddecf6" origin="Generated by Gradle"/> <sha256 value="2994a7eb78f2710bd3d3bfb639b2c94e219cedac0d4d084d516e78c16dddecf6" origin="Generated by Gradle"/>
</artifact> </artifact>
<artifact name="j2objc-annotations-1.1.pom"> <artifact name="j2objc-annotations-1.1.pom">
@ -1387,6 +1452,7 @@
</component> </component>
<component group="com.googlecode.juniversalchardet" name="juniversalchardet" version="1.0.3"> <component group="com.googlecode.juniversalchardet" name="juniversalchardet" version="1.0.3">
<artifact name="juniversalchardet-1.0.3.jar"> <artifact name="juniversalchardet-1.0.3.jar">
<pgp value="31bae2e51d95e0f8ad9b7bcc40a3c4432bd7308c"/>
<sha256 value="757bfe906193b8b651e79dc26cd67d6b55d0770a2cdfb0381591504f779d4a76" origin="Generated by Gradle"/> <sha256 value="757bfe906193b8b651e79dc26cd67d6b55d0770a2cdfb0381591504f779d4a76" origin="Generated by Gradle"/>
</artifact> </artifact>
<artifact name="juniversalchardet-1.0.3.pom"> <artifact name="juniversalchardet-1.0.3.pom">
@ -1463,6 +1529,7 @@
</component> </component>
<component group="com.sun.activation" name="javax.activation" version="1.2.0"> <component group="com.sun.activation" name="javax.activation" version="1.2.0">
<artifact name="javax.activation-1.2.0.jar"> <artifact name="javax.activation-1.2.0.jar">
<pgp value="4f7e32d440ef90a83011a8fc6425559c47cc79c4"/>
<sha256 value="993302b16cd7056f21e779cc577d175a810bb4900ef73cd8fbf2b50f928ba9ce" origin="Generated by Gradle"/> <sha256 value="993302b16cd7056f21e779cc577d175a810bb4900ef73cd8fbf2b50f928ba9ce" origin="Generated by Gradle"/>
</artifact> </artifact>
<artifact name="javax.activation-1.2.0.pom"> <artifact name="javax.activation-1.2.0.pom">
@ -1535,6 +1602,7 @@
</component> </component>
<component group="commons-codec" name="commons-codec" version="1.10"> <component group="commons-codec" name="commons-codec" version="1.10">
<artifact name="commons-codec-1.10.jar"> <artifact name="commons-codec-1.10.jar">
<pgp value="2db4f1ef0fa761ecc4ea935c86fdc7e2a11262cb"/>
<sha256 value="4241dfa94e711d435f29a4604a3e2de5c4aa3c165e23bd066be6fc1fc4309569" origin="Generated by Gradle"/> <sha256 value="4241dfa94e711d435f29a4604a3e2de5c4aa3c165e23bd066be6fc1fc4309569" origin="Generated by Gradle"/>
</artifact> </artifact>
<artifact name="commons-codec-1.10.pom"> <artifact name="commons-codec-1.10.pom">
@ -1552,6 +1620,7 @@
</component> </component>
<component group="commons-io" name="commons-io" version="2.4"> <component group="commons-io" name="commons-io" version="2.4">
<artifact name="commons-io-2.4.jar"> <artifact name="commons-io-2.4.jar">
<pgp value="2db4f1ef0fa761ecc4ea935c86fdc7e2a11262cb"/>
<sha256 value="cc6a41dc3eaacc9e440a6bd0d2890b20d36b4ee408fe2d67122f328bb6e01581" origin="Generated by Gradle"/> <sha256 value="cc6a41dc3eaacc9e440a6bd0d2890b20d36b4ee408fe2d67122f328bb6e01581" origin="Generated by Gradle"/>
</artifact> </artifact>
<artifact name="commons-io-2.4.pom"> <artifact name="commons-io-2.4.pom">
@ -1569,6 +1638,7 @@
</component> </component>
<component group="commons-logging" name="commons-logging" version="1.2"> <component group="commons-logging" name="commons-logging" version="1.2">
<artifact name="commons-logging-1.2.jar"> <artifact name="commons-logging-1.2.jar">
<pgp value="0cc641c3a62453ab390066c4a41f13c999945293"/>
<sha256 value="daddea1ea0be0f56978ab3006b8ac92834afeefbd9b7e4e6316fca57df0fa636" origin="Generated by Gradle"/> <sha256 value="daddea1ea0be0f56978ab3006b8ac92834afeefbd9b7e4e6316fca57df0fa636" origin="Generated by Gradle"/>
</artifact> </artifact>
<artifact name="commons-logging-1.2.pom"> <artifact name="commons-logging-1.2.pom">
@ -1611,6 +1681,7 @@
</component> </component>
<component group="it.unimi.dsi" name="fastutil" version="7.2.0"> <component group="it.unimi.dsi" name="fastutil" version="7.2.0">
<artifact name="fastutil-7.2.0.jar"> <artifact name="fastutil-7.2.0.jar">
<pgp value="8254180bfc943b816e0b5e2e5e2f2b3d474efe6b"/>
<sha256 value="74fa208043740642f7e6eb09faba15965218ad2f50ce3020efb100136e4b591c" origin="Generated by Gradle"/> <sha256 value="74fa208043740642f7e6eb09faba15965218ad2f50ce3020efb100136e4b591c" origin="Generated by Gradle"/>
</artifact> </artifact>
<artifact name="fastutil-7.2.0.pom"> <artifact name="fastutil-7.2.0.pom">
@ -1752,6 +1823,7 @@
</component> </component>
<component group="net.sf.jopt-simple" name="jopt-simple" version="4.9"> <component group="net.sf.jopt-simple" name="jopt-simple" version="4.9">
<artifact name="jopt-simple-4.9.jar"> <artifact name="jopt-simple-4.9.jar">
<pgp value="517b94f8d0a46317a28d8ab30da8a5ec02d11ead"/>
<sha256 value="26c5856e954b5f864db76f13b86919b59c6eecf9fd930b96baa8884626baf2f5" origin="Generated by Gradle"/> <sha256 value="26c5856e954b5f864db76f13b86919b59c6eecf9fd930b96baa8884626baf2f5" origin="Generated by Gradle"/>
</artifact> </artifact>
<artifact name="jopt-simple-4.9.pom"> <artifact name="jopt-simple-4.9.pom">
@ -1916,6 +1988,7 @@
</component> </component>
<component group="org.apache.commons" name="commons-compress" version="1.12"> <component group="org.apache.commons" name="commons-compress" version="1.12">
<artifact name="commons-compress-1.12.jar"> <artifact name="commons-compress-1.12.jar">
<pgp value="ce8075a251547bee249bc151a2115ae15f6b8b72"/>
<sha256 value="2c1542faf343185b7cab9c3d55c8ae5471d6d095d3887a4adefdbdf2984dc0b6" origin="Generated by Gradle"/> <sha256 value="2c1542faf343185b7cab9c3d55c8ae5471d6d095d3887a4adefdbdf2984dc0b6" origin="Generated by Gradle"/>
</artifact> </artifact>
<artifact name="commons-compress-1.12.pom"> <artifact name="commons-compress-1.12.pom">
@ -2195,6 +2268,7 @@
</component> </component>
<component group="org.checkerframework" name="checker-qual" version="2.5.2"> <component group="org.checkerframework" name="checker-qual" version="2.5.2">
<artifact name="checker-qual-2.5.2.jar"> <artifact name="checker-qual-2.5.2.jar">
<pgp value="19beab2d799c020f17c69126b16698a4adf4d638"/>
<sha256 value="64b02691c8b9d4e7700f8ee2e742dce7ea2c6e81e662b7522c9ee3bf568c040a" origin="Generated by Gradle"/> <sha256 value="64b02691c8b9d4e7700f8ee2e742dce7ea2c6e81e662b7522c9ee3bf568c040a" origin="Generated by Gradle"/>
</artifact> </artifact>
<artifact name="checker-qual-2.5.2.pom"> <artifact name="checker-qual-2.5.2.pom">
@ -2224,6 +2298,7 @@
</component> </component>
<component group="org.codehaus.groovy" name="groovy-all" version="2.4.15"> <component group="org.codehaus.groovy" name="groovy-all" version="2.4.15">
<artifact name="groovy-all-2.4.15.jar"> <artifact name="groovy-all-2.4.15.jar">
<pgp value="7faa0f2206de228f0db01ad741321490758aad6f"/>
<sha256 value="51d6c4e71782e85674239189499854359d380fb75e1a703756e3aaa5b98a5af0" origin="Generated by Gradle"/> <sha256 value="51d6c4e71782e85674239189499854359d380fb75e1a703756e3aaa5b98a5af0" origin="Generated by Gradle"/>
</artifact> </artifact>
<artifact name="groovy-all-2.4.15.pom"> <artifact name="groovy-all-2.4.15.pom">
@ -2242,6 +2317,7 @@
</component> </component>
<component group="org.codehaus.mojo" name="animal-sniffer-annotations" version="1.17"> <component group="org.codehaus.mojo" name="animal-sniffer-annotations" version="1.17">
<artifact name="animal-sniffer-annotations-1.17.jar"> <artifact name="animal-sniffer-annotations-1.17.jar">
<pgp value="f254b35617dc255d9344bcfa873a8e86b4372146"/>
<sha256 value="92654f493ecfec52082e76354f0ebf87648dc3d5cec2e3c3cdb947c016747a53" origin="Generated by Gradle"/> <sha256 value="92654f493ecfec52082e76354f0ebf87648dc3d5cec2e3c3cdb947c016747a53" origin="Generated by Gradle"/>
</artifact> </artifact>
<artifact name="animal-sniffer-annotations-1.17.pom"> <artifact name="animal-sniffer-annotations-1.17.pom">
@ -2374,6 +2450,7 @@
</component> </component>
<component group="org.jdom" name="jdom2" version="2.0.6"> <component group="org.jdom" name="jdom2" version="2.0.6">
<artifact name="jdom2-2.0.6.jar"> <artifact name="jdom2-2.0.6.jar">
<pgp value="5897253bea3046aeea95a067e93671c7272b7b3f"/>
<sha256 value="1345f11ba606d15603d6740551a8c21947c0215640770ec67271fe78bea97cf5" origin="Generated by Gradle"/> <sha256 value="1345f11ba606d15603d6740551a8c21947c0215640770ec67271fe78bea97cf5" origin="Generated by Gradle"/>
</artifact> </artifact>
<artifact name="jdom2-2.0.6.pom"> <artifact name="jdom2-2.0.6.pom">
@ -2382,6 +2459,7 @@
</component> </component>
<component group="org.jetbrains" name="annotations" version="13.0"> <component group="org.jetbrains" name="annotations" version="13.0">
<artifact name="annotations-13.0.jar"> <artifact name="annotations-13.0.jar">
<pgp value="2e3a1affe42b5f53af19f780bcf4173966770193"/>
<sha256 value="ace2a10dc8e2d5fd34925ecac03e4988b2c0f851650c94b8cef49ba1bd111478" origin="Generated by Gradle"/> <sha256 value="ace2a10dc8e2d5fd34925ecac03e4988b2c0f851650c94b8cef49ba1bd111478" origin="Generated by Gradle"/>
</artifact> </artifact>
<artifact name="annotations-13.0.pom"> <artifact name="annotations-13.0.pom">