From df66d127c27044ab7420f7c13c50c8516e584066 Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Sat, 26 Dec 2020 10:08:14 +0530 Subject: [PATCH 1/8] Add AndroidX WorkManager. --- app/build.gradle | 1 + gradle/verification-metadata.xml | 61 +++++++++++++++++++++++++++++++- 2 files changed, 61 insertions(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 26d2a0a17..a179fe9f9 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -145,6 +145,7 @@ dependencies { implementation 'androidx.vectordrawable:vectordrawable:1.1.0' implementation 'androidx.constraintlayout:constraintlayout:1.1.3' implementation 'androidx.palette:palette:1.0.0' + implementation 'androidx.work:work-runtime:2.4.0' implementation 'com.google.android.material:material:1.1.0' diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index a0a56000a..d5aae5a02 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -79,7 +79,10 @@ - + + + + @@ -346,6 +349,11 @@ + + + + + @@ -367,6 +375,11 @@ + + + + + @@ -439,6 +452,16 @@ + + + + + + + + + + @@ -455,6 +478,16 @@ + + + + + + + + + + @@ -610,6 +643,11 @@ + + + + + @@ -768,6 +806,9 @@ + + + @@ -854,6 +895,7 @@ + @@ -1143,6 +1185,7 @@ + @@ -1173,6 +1216,7 @@ + @@ -1181,6 +1225,7 @@ + @@ -1306,6 +1351,7 @@ + @@ -1387,6 +1433,7 @@ + @@ -1463,6 +1510,7 @@ + @@ -1535,6 +1583,7 @@ + @@ -1552,6 +1601,7 @@ + @@ -1569,6 +1619,7 @@ + @@ -1611,6 +1662,7 @@ + @@ -1752,6 +1804,7 @@ + @@ -1916,6 +1969,7 @@ + @@ -2195,6 +2249,7 @@ + @@ -2224,6 +2279,7 @@ + @@ -2242,6 +2298,7 @@ + @@ -2374,6 +2431,7 @@ + @@ -2382,6 +2440,7 @@ + From 7c81b1ad157437c3d5881c7cc56713085d2177bd Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Sat, 26 Dec 2020 11:03:19 +0530 Subject: [PATCH 2/8] Use WorkManager to clean the cache. --- ...iceTest.java => CleanCacheWorkerTest.java} | 17 +- app/src/main/AndroidManifest.xml | 8 - .../fdroid/fdroid/CleanCacheJobService.java | 22 --- .../fdroid/fdroid/CleanCacheService21.java | 30 ---- .../java/org/fdroid/fdroid/FDroidApp.java | 26 +-- .../receiver/DeviceStorageReceiver.java | 5 +- .../fdroid/views/PreferencesFragment.java | 4 +- .../CleanCacheWorker.java} | 151 +++++++++--------- .../org/fdroid/fdroid/work/WorkUtils.java | 49 ++++++ 9 files changed, 151 insertions(+), 161 deletions(-) rename app/src/androidTest/java/org/fdroid/fdroid/{CleanCacheServiceTest.java => CleanCacheWorkerTest.java} (81%) delete mode 100644 app/src/main/java/org/fdroid/fdroid/CleanCacheJobService.java delete mode 100644 app/src/main/java/org/fdroid/fdroid/CleanCacheService21.java rename app/src/main/java/org/fdroid/fdroid/{CleanCacheService.java => work/CleanCacheWorker.java} (50%) create mode 100644 app/src/main/java/org/fdroid/fdroid/work/WorkUtils.java diff --git a/app/src/androidTest/java/org/fdroid/fdroid/CleanCacheServiceTest.java b/app/src/androidTest/java/org/fdroid/fdroid/CleanCacheWorkerTest.java similarity index 81% rename from app/src/androidTest/java/org/fdroid/fdroid/CleanCacheServiceTest.java rename to app/src/androidTest/java/org/fdroid/fdroid/CleanCacheWorkerTest.java index 7178bd34a..e3706dc03 100644 --- a/app/src/androidTest/java/org/fdroid/fdroid/CleanCacheServiceTest.java +++ b/app/src/androidTest/java/org/fdroid/fdroid/CleanCacheWorkerTest.java @@ -1,11 +1,13 @@ package org.fdroid.fdroid; import android.app.Instrumentation; -import androidx.test.platform.app.InstrumentationRegistry; + import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; import org.apache.commons.io.FileUtils; import org.fdroid.fdroid.compat.FileCompatTest; +import org.fdroid.fdroid.work.CleanCacheWorker; import org.junit.Test; import org.junit.runner.RunWith; @@ -16,9 +18,8 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @RunWith(AndroidJUnit4.class) -public class CleanCacheServiceTest { - - public static final String TAG = "CleanCacheServiceTest"; +public class CleanCacheWorkerTest { + public static final String TAG = "CleanCacheWorkerTest"; @Test public void testClearOldFiles() throws IOException, InterruptedException { @@ -48,18 +49,18 @@ public class CleanCacheServiceTest { assertTrue(second.createNewFile()); assertTrue(second.exists()); - CleanCacheService.clearOldFiles(dir, 3000); // check all in dir + CleanCacheWorker.clearOldFiles(dir, 3000); // check all in dir assertFalse(first.exists()); assertTrue(second.exists()); Thread.sleep(7000); - CleanCacheService.clearOldFiles(second, 3000); // check just second file + 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"); - CleanCacheService.clearOldFiles(nonexistent, 1); - CleanCacheService.clearOldFiles(null, 1); + CleanCacheWorker.clearOldFiles(nonexistent, 1); + CleanCacheWorker.clearOldFiles(null, 1); } } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index d8278645e..80592fbb3 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -241,14 +241,6 @@ android:name=".installer.InstallerService" android:permission="android.permission.BIND_JOB_SERVICE" android:exported="false"/> - - * 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 diff --git a/app/src/main/java/org/fdroid/fdroid/receiver/DeviceStorageReceiver.java b/app/src/main/java/org/fdroid/fdroid/receiver/DeviceStorageReceiver.java index b4f1fbfe1..6e0d10c18 100644 --- a/app/src/main/java/org/fdroid/fdroid/receiver/DeviceStorageReceiver.java +++ b/app/src/main/java/org/fdroid/fdroid/receiver/DeviceStorageReceiver.java @@ -3,9 +3,10 @@ package org.fdroid.fdroid.receiver; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; -import org.fdroid.fdroid.CleanCacheService; + import org.fdroid.fdroid.DeleteCacheService; import org.fdroid.fdroid.Utils; +import org.fdroid.fdroid.work.WorkUtils; public class DeviceStorageReceiver extends BroadcastReceiver { @Override @@ -18,7 +19,7 @@ public class DeviceStorageReceiver extends BroadcastReceiver { int percentageFree = Utils.getPercent(Utils.getImageCacheDirAvailableMemory(context), Utils.getImageCacheDirTotalMemory(context)); if (percentageFree > 2) { - CleanCacheService.start(context); + WorkUtils.scheduleCleanCache(context); } else { DeleteCacheService.deleteAll(context); } diff --git a/app/src/main/java/org/fdroid/fdroid/views/PreferencesFragment.java b/app/src/main/java/org/fdroid/fdroid/views/PreferencesFragment.java index a6e2ba4bf..e81734779 100644 --- a/app/src/main/java/org/fdroid/fdroid/views/PreferencesFragment.java +++ b/app/src/main/java/org/fdroid/fdroid/views/PreferencesFragment.java @@ -48,7 +48,6 @@ import androidx.preference.SwitchPreference; import androidx.recyclerview.widget.LinearSmoothScroller; import androidx.recyclerview.widget.RecyclerView; -import org.fdroid.fdroid.CleanCacheService; import org.fdroid.fdroid.FDroidApp; import org.fdroid.fdroid.Languages; import org.fdroid.fdroid.Preferences; @@ -58,6 +57,7 @@ import org.fdroid.fdroid.Utils; import org.fdroid.fdroid.data.RepoProvider; import org.fdroid.fdroid.installer.InstallHistoryService; import org.fdroid.fdroid.installer.PrivilegedInstaller; +import org.fdroid.fdroid.work.WorkUtils; import info.guardianproject.netcipher.proxy.OrbotHelper; @@ -304,7 +304,7 @@ public class PreferencesFragment extends PreferenceFragmentCompat entrySummary(key); if (changing && currentKeepCacheTime != Preferences.get().getKeepCacheTime()) { - CleanCacheService.schedule(getActivity()); + WorkUtils.scheduleCleanCache(requireContext()); } break; diff --git a/app/src/main/java/org/fdroid/fdroid/CleanCacheService.java b/app/src/main/java/org/fdroid/fdroid/work/CleanCacheWorker.java similarity index 50% rename from app/src/main/java/org/fdroid/fdroid/CleanCacheService.java rename to app/src/main/java/org/fdroid/fdroid/work/CleanCacheWorker.java index 618df2f10..a6407d84e 100644 --- a/app/src/main/java/org/fdroid/fdroid/CleanCacheService.java +++ b/app/src/main/java/org/fdroid/fdroid/work/CleanCacheWorker.java @@ -1,90 +1,45 @@ -package org.fdroid.fdroid; +package org.fdroid.fdroid.work; -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 android.system.ErrnoException; +import android.system.Os; +import android.system.StructStat; + import androidx.annotation.NonNull; -import androidx.core.app.JobIntentService; -import androidx.core.content.ContextCompat; +import androidx.annotation.RequiresApi; +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; -/** - * 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()}. - *

- * 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"; +public class CleanCacheWorker extends Worker { + private static final String TAG = CleanCacheWorker.class.getSimpleName(); - 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)); + public CleanCacheWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) { + super(context, workerParams); } + @NonNull @Override - protected void onHandleWork(@NonNull Intent intent) { + public Result doWork() { Process.setThreadPriority(Process.THREAD_PRIORITY_LOWEST); - deleteExpiredApksFromCache(); - deleteStrayIndexFiles(); - deleteOldInstallerFiles(); - deleteOldIcons(); + try { + deleteExpiredApksFromCache(); + deleteStrayIndexFiles(); + deleteOldInstallerFiles(); + deleteOldIcons(); + return Result.success(); + } catch (Exception e) { + return Result.failure(); + } } /** @@ -93,7 +48,7 @@ public class CleanCacheService extends JobIntentService { * any APK in the cache that is older than that preference specifies. */ private void deleteExpiredApksFromCache() { - File cacheDir = ApkCache.getApkCacheDir(getBaseContext()); + File cacheDir = ApkCache.getApkCacheDir(getApplicationContext()); clearOldFiles(cacheDir, Preferences.get().getKeepCacheTime()); } @@ -102,13 +57,15 @@ public class CleanCacheService extends JobIntentService { * a safe place before installing. It doesn't clean up them reliably yet. */ private void deleteOldInstallerFiles() { - File filesDir = getFilesDir(); + 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; } @@ -132,13 +89,15 @@ public class CleanCacheService extends JobIntentService { * {@link org.fdroid.fdroid.net.DownloaderFactory#create(Context, String)}, e.g. "dl-*" */ private void deleteStrayIndexFiles() { - File cacheDir = getCacheDir(); + 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; } @@ -156,7 +115,7 @@ public class CleanCacheService extends JobIntentService { * Delete cached icons that have not been accessed in over a year. */ private void deleteOldIcons() { - clearOldFiles(Utils.getImageCacheDir(this), TimeUnit.DAYS.toMillis(365)); + clearOldFiles(Utils.getImageCacheDir(getApplicationContext()), TimeUnit.DAYS.toMillis(365)); } /** @@ -170,24 +129,58 @@ public class CleanCacheService extends JobIntentService { */ 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); } - f.delete(); - } else if (Build.VERSION.SDK_INT < 21) { + deleteFileAndLog(f); + } else if (Build.VERSION.SDK_INT <= 21) { if (FileUtils.isFileOlder(f, olderThan)) { - f.delete(); + deleteFileAndLog(f); } } else { - CleanCacheService21.deleteIfOld(f, olderThan); + Impl21.deleteIfOld(f, olderThan); } } -} \ No newline at end of file + + 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); + } + } + } +} diff --git a/app/src/main/java/org/fdroid/fdroid/work/WorkUtils.java b/app/src/main/java/org/fdroid/fdroid/work/WorkUtils.java new file mode 100644 index 000000000..1314231ff --- /dev/null +++ b/app/src/main/java/org/fdroid/fdroid/work/WorkUtils.java @@ -0,0 +1,49 @@ +package org.fdroid.fdroid.work; + +import android.content.Context; +import android.os.Build; + +import androidx.annotation.NonNull; +import androidx.work.Constraints; +import androidx.work.ExistingPeriodicWorkPolicy; +import androidx.work.PeriodicWorkRequest; +import androidx.work.WorkManager; + +import org.fdroid.fdroid.Preferences; +import org.fdroid.fdroid.Utils; + +import java.util.concurrent.TimeUnit; + +public class WorkUtils { + private static final String TAG = WorkUtils.class.getSimpleName(); + + private WorkUtils() { } + + /** + * 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 scheduleCleanCache(@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("clean_cache", + ExistingPeriodicWorkPolicy.REPLACE, cleanCache); + Utils.debugLog(TAG, "Scheduled periodic work for cleaning the cache."); + } +} From ab8e7935b0ea12fff5dd45fdef6c2cc36748722d Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Wed, 6 Jan 2021 05:22:23 +0530 Subject: [PATCH 3/8] Add WorkManagerTestRule. --- app/build.gradle | 8 +++- .../fdroid/fdroid/WorkManagerTestRule.java | 35 ++++++++++++++++++ gradle/verification-keyring.gpg | Bin 217236 -> 220017 bytes gradle/verification-metadata.xml | 21 ++++++++++- 4 files changed, 62 insertions(+), 2 deletions(-) create mode 100644 app/src/androidTest/java/org/fdroid/fdroid/WorkManagerTestRule.java diff --git a/app/build.gradle b/app/build.gradle index a179fe9f9..d72191717 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -135,6 +135,10 @@ android { } } +ext { + work_version = '2.4.0' +} + dependencies { implementation 'androidx.appcompat:appcompat:1.1.0' implementation 'androidx.preference:preference:1.1.1' @@ -145,7 +149,7 @@ dependencies { implementation 'androidx.vectordrawable:vectordrawable:1.1.0' implementation 'androidx.constraintlayout:constraintlayout:1.1.3' implementation 'androidx.palette:palette:1.0.0' - implementation 'androidx.work:work-runtime:2.4.0' + implementation "androidx.work:work-runtime:$work_version" implementation 'com.google.android.material:material:1.1.0' @@ -177,6 +181,7 @@ dependencies { testImplementation 'org.hamcrest:hamcrest:2.2' 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:runner:1.3.0' androidTestImplementation 'androidx.test:rules:1.3.0' @@ -184,6 +189,7 @@ dependencies { androidTestImplementation 'androidx.test.ext:junit:1.1.2' androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' androidTestImplementation 'androidx.test.uiautomator:uiautomator:2.2.0' + androidTestImplementation "androidx.work:work-testing:$work_version" } checkstyle { diff --git a/app/src/androidTest/java/org/fdroid/fdroid/WorkManagerTestRule.java b/app/src/androidTest/java/org/fdroid/fdroid/WorkManagerTestRule.java new file mode 100644 index 000000000..a2091588b --- /dev/null +++ b/app/src/androidTest/java/org/fdroid/fdroid/WorkManagerTestRule.java @@ -0,0 +1,35 @@ +package org.fdroid.fdroid; + +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 { + private Context targetContext; + private Context testContext; + private Configuration configuration; + private 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); + } +} diff --git a/gradle/verification-keyring.gpg b/gradle/verification-keyring.gpg index f506bf2774fc067aa5257b4b22a15e1d11ae333a..6349fbeee46b66c0d845e4fe5e1c1fc0b90a422d 100644 GIT binary patch delta 2792 zcmV5s+g-{wR!a{VUF{7ENrvqixOb|MP> z|KM3Tj>=Q8A!`pZeI9?KW3nkN#QD9mIh1F;k&gfo0RRECC_-s&baG*NAShyaAWBAZ zZ)YhWJYs2XbaG*NKw@cbbaG*NE@N+PK8XS|1P}oV038+~1pdE>D0u)0%0tG*4YRFG z?3aRrWd#CX;oN5c3JDOitxW8ff`etp6%YLzN5zvJdKj4yF!6sO+xcw$h$r`RI~n4y z3+S%U0{#fXb_92#|B8CKYAFXHejAe4$w_Xa_#SeTytWFyuKC2=gYiitTXM~?Xg;#Sdqan(& ziql2hUoXZ%-o<}zrLPtQbm=@2x&Mv8X3OJ8MkJ?eSQ?Io&8Ul+u|G$g(bcb_(S#F5 zU1D%~Pi_2bFZad?rcQy&JpI!x&?g%v7JLf{-CjkU+@5TX7ueRpM!~*FW|3|jo@Eyf zYB1m|YJltHPRli3D=dj&V67yuh900LG8qGE;lEWb_eFo4mDd#v-|`76xjRKJQb74b0h&H=DewJ_(RIMS#n3zkOwgV5Q9XtG zl0f8~Ig@`;Ff@6PXjrfavl>1Sk~nQQzfHs%)A6_*mR0JV>!G|gBQO?~VfIj}Vu$pc z3(sK^yKoz7rrk(!GyWF2SFOF*yFX#8r^Czoxz3PHSxOe&s~=GmL87RZw;7YhWq&&V zQc?}p>%7x)mbfScZllk`BA=^u?}yEa0yqQ{0RjjBA_W3e;_#A}a8&{ze>a@rlGFjK zVK=%D|7F5ye0)-Id|jO*!Hqo0kpl_d!tXgHC4~KlH)+{cFXL)n6)3be(t$j5)bS=WT@HW@s7bA=Rc%`qRqlNtVV33Oz+P{$kDsDQ&6Us8$=L)%W4^ zq95r6RHKk1m8CFi;9(T*f3i+#eB?^=zuxyX#TM(F;OU?$1+*o7+G@OjA9We}_j^sa%&pfEy&@Tc2lcx`XO4Rl>E2kMOJCsHT`XYE| zn4|r^u%?9-h^zk2pwX=)o5Jb!*H`?ke%4x7Lf)|~AC!D2=;r5x`KJKCE^uJyaj&8J zXS4)MHAC-kRcY*af9`vE8%c^{5YTlbzt&0r|vQjOuT!*;jboj3sP~y>n@7k;spBgzk zVUxwogB5QT=E7nN(l%lX+5<|HF(1&Ccejcg_b)8*s%K1hxRUf^S?Q`G^Vi&l3ZV5$9rQ(>+&Fh3{8y zd>1jp>VDpj2%p%)RFJWIpjXn8bPJFSHCpPV!HM@le~KJ_pBh`6x7UAfS!8)vY>Qdp zh}ZAzr7~(i$Cn0ievI|5iVwz+ajRG<{xLq+zr@qq1bD63UbaRLoby-Gw!V!-S#Bq~ zwC}>?<&$DNt4%Dy4kPh2w=u;-W31ydA-4-I6p=$f(-gRZ=b--OOV1%jKN=64nsQF@+3?@0~uJRnR6i+qX8aHHNGn+ zT}?l0Qs^K901*KI0f_=11Q-DV00{*GRO0ZG0vikf3JDN5oZ*tx0jpumk`MryA@A|L zxWqmfm9pHKBh|hL3s}(OV-;tCc1CCk?^R2=dq;kwpaxZ`$T^ zVe*z%evi1*i=z;n$RlJV=_(nj7V6s?(|{`LD11ibYJO{eSla)S3&W%U-Nw7p!GnKo z)1NT{uws%=2#$AxY>Goyk#FShNz`kFe-Lp;*wQGKp{b-ptijy?F3s~gWIQP5aV@R} zo_>9TL^}V#+t)SITT~Q5XtI{^ZF@?-eZ;VYp&%rzvNuX?lZH0#w35KSeXB8ybf(ReDsnjlp~e}81K zp<5NF?ApRT>{IqbB#^r395a0?4(mYJDk$f~r~!Z&GwlhKq_DtLN77+~JSecmEY7=t zO_Yi;LLJ1;yC#b$f#Ma9pW3rul}y-$@3g&E&i^Y?+CVl-DD=bQu)rVHkIDoVl$%5z zhJ!E(VSm)DEEmxYyR2xRiGCURN)LwMtPpV>sbUowdc!R%Auwip9o&GmLC;-rRnSnA u+^Ut~NQtg6Grzd%eI%5zbo@d72l^1al{eA_u}yyy_2EB+Bt)9oBT;|rO-NP% delta 15 Wcmeykjd#jI-i8*&7N#xCQvv`tLIxcG diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index d5aae5a02..5ce758fee 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -63,6 +63,7 @@ + @@ -86,7 +87,10 @@ - + + + + @@ -166,6 +170,11 @@ + + + + + @@ -648,6 +657,16 @@ + + + + + + + + + + From fa7e7fccdc88f3b24f3df34c8c3f61df5d7e7372 Mon Sep 17 00:00:00 2001 From: Hans-Christoph Steiner Date: Mon, 25 Jan 2021 15:15:26 +0100 Subject: [PATCH 4/8] fdroidclient does not use variables for gradle dependencies Using variables makes the lint stuff not work so well. --- app/build.gradle | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index d72191717..7390a9974 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -135,10 +135,6 @@ android { } } -ext { - work_version = '2.4.0' -} - dependencies { implementation 'androidx.appcompat:appcompat:1.1.0' implementation 'androidx.preference:preference:1.1.1' @@ -149,7 +145,7 @@ dependencies { implementation 'androidx.vectordrawable:vectordrawable:1.1.0' implementation 'androidx.constraintlayout:constraintlayout:1.1.3' implementation 'androidx.palette:palette:1.0.0' - implementation "androidx.work:work-runtime:$work_version" + implementation 'androidx.work:work-runtime:2.4.0' implementation 'com.google.android.material:material:1.1.0' @@ -189,7 +185,7 @@ dependencies { androidTestImplementation 'androidx.test.ext:junit:1.1.2' androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' androidTestImplementation 'androidx.test.uiautomator:uiautomator:2.2.0' - androidTestImplementation "androidx.work:work-testing:$work_version" + androidTestImplementation 'androidx.work:work-testing:2.4.0' } checkstyle { From 2e10c7ba03e38216b27c399ed77aed7e96a2f3d8 Mon Sep 17 00:00:00 2001 From: Hans-Christoph Steiner Date: Tue, 5 Jan 2021 10:47:18 +0100 Subject: [PATCH 5/8] move static helper method into its class: CleanCacheWorker --- .../java/org/fdroid/fdroid/FDroidApp.java | 3 +- .../receiver/DeviceStorageReceiver.java | 4 +- .../fdroid/views/PreferencesFragment.java | 4 +- .../fdroid/fdroid/work/CleanCacheWorker.java | 36 ++++++++++++-- .../org/fdroid/fdroid/work/WorkUtils.java | 49 ------------------- 5 files changed, 38 insertions(+), 58 deletions(-) delete mode 100644 app/src/main/java/org/fdroid/fdroid/work/WorkUtils.java diff --git a/app/src/main/java/org/fdroid/fdroid/FDroidApp.java b/app/src/main/java/org/fdroid/fdroid/FDroidApp.java index f8b53b345..f79e45688 100644 --- a/app/src/main/java/org/fdroid/fdroid/FDroidApp.java +++ b/app/src/main/java/org/fdroid/fdroid/FDroidApp.java @@ -80,7 +80,6 @@ import org.fdroid.fdroid.net.HttpDownloader; import org.fdroid.fdroid.net.ImageLoaderForUIL; import org.fdroid.fdroid.panic.HidingManager; import org.fdroid.fdroid.work.CleanCacheWorker; -import org.fdroid.fdroid.work.WorkUtils; import java.io.IOException; import java.nio.ByteBuffer; @@ -427,7 +426,7 @@ public class FDroidApp extends Application { } }); - WorkUtils.scheduleCleanCache(this); + CleanCacheWorker.schedule(this); notificationHelper = new NotificationHelper(getApplicationContext()); diff --git a/app/src/main/java/org/fdroid/fdroid/receiver/DeviceStorageReceiver.java b/app/src/main/java/org/fdroid/fdroid/receiver/DeviceStorageReceiver.java index 6e0d10c18..08b33d394 100644 --- a/app/src/main/java/org/fdroid/fdroid/receiver/DeviceStorageReceiver.java +++ b/app/src/main/java/org/fdroid/fdroid/receiver/DeviceStorageReceiver.java @@ -6,7 +6,7 @@ import android.content.Intent; import org.fdroid.fdroid.DeleteCacheService; import org.fdroid.fdroid.Utils; -import org.fdroid.fdroid.work.WorkUtils; +import org.fdroid.fdroid.work.CleanCacheWorker; public class DeviceStorageReceiver extends BroadcastReceiver { @Override @@ -19,7 +19,7 @@ public class DeviceStorageReceiver extends BroadcastReceiver { int percentageFree = Utils.getPercent(Utils.getImageCacheDirAvailableMemory(context), Utils.getImageCacheDirTotalMemory(context)); if (percentageFree > 2) { - WorkUtils.scheduleCleanCache(context); + CleanCacheWorker.schedule(context); } else { DeleteCacheService.deleteAll(context); } diff --git a/app/src/main/java/org/fdroid/fdroid/views/PreferencesFragment.java b/app/src/main/java/org/fdroid/fdroid/views/PreferencesFragment.java index e81734779..4caf90faa 100644 --- a/app/src/main/java/org/fdroid/fdroid/views/PreferencesFragment.java +++ b/app/src/main/java/org/fdroid/fdroid/views/PreferencesFragment.java @@ -57,7 +57,7 @@ import org.fdroid.fdroid.Utils; import org.fdroid.fdroid.data.RepoProvider; import org.fdroid.fdroid.installer.InstallHistoryService; import org.fdroid.fdroid.installer.PrivilegedInstaller; -import org.fdroid.fdroid.work.WorkUtils; +import org.fdroid.fdroid.work.CleanCacheWorker; import info.guardianproject.netcipher.proxy.OrbotHelper; @@ -304,7 +304,7 @@ public class PreferencesFragment extends PreferenceFragmentCompat entrySummary(key); if (changing && currentKeepCacheTime != Preferences.get().getKeepCacheTime()) { - WorkUtils.scheduleCleanCache(requireContext()); + CleanCacheWorker.schedule(requireContext()); } break; diff --git a/app/src/main/java/org/fdroid/fdroid/work/CleanCacheWorker.java b/app/src/main/java/org/fdroid/fdroid/work/CleanCacheWorker.java index a6407d84e..e5ba902f4 100644 --- a/app/src/main/java/org/fdroid/fdroid/work/CleanCacheWorker.java +++ b/app/src/main/java/org/fdroid/fdroid/work/CleanCacheWorker.java @@ -6,12 +6,14 @@ 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; @@ -21,12 +23,40 @@ import java.io.File; import java.util.concurrent.TimeUnit; public class CleanCacheWorker extends Worker { - private static final String TAG = CleanCacheWorker.class.getSimpleName(); + 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("clean_cache", + ExistingPeriodicWorkPolicy.REPLACE, cleanCache); + Utils.debugLog(TAG, "Scheduled periodic work for cleaning the cache."); + } + @NonNull @Override public Result doWork() { diff --git a/app/src/main/java/org/fdroid/fdroid/work/WorkUtils.java b/app/src/main/java/org/fdroid/fdroid/work/WorkUtils.java deleted file mode 100644 index 1314231ff..000000000 --- a/app/src/main/java/org/fdroid/fdroid/work/WorkUtils.java +++ /dev/null @@ -1,49 +0,0 @@ -package org.fdroid.fdroid.work; - -import android.content.Context; -import android.os.Build; - -import androidx.annotation.NonNull; -import androidx.work.Constraints; -import androidx.work.ExistingPeriodicWorkPolicy; -import androidx.work.PeriodicWorkRequest; -import androidx.work.WorkManager; - -import org.fdroid.fdroid.Preferences; -import org.fdroid.fdroid.Utils; - -import java.util.concurrent.TimeUnit; - -public class WorkUtils { - private static final String TAG = WorkUtils.class.getSimpleName(); - - private WorkUtils() { } - - /** - * 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 scheduleCleanCache(@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("clean_cache", - ExistingPeriodicWorkPolicy.REPLACE, cleanCache); - Utils.debugLog(TAG, "Scheduled periodic work for cleaning the cache."); - } -} From 06dbd048af788af10fc004de8bc7dbff495be2d2 Mon Sep 17 00:00:00 2001 From: Hans-Christoph Steiner Date: Wed, 6 Jan 2021 16:11:57 +0100 Subject: [PATCH 6/8] add WorkManagerTestRule to CleanCacheWorkerTest --- .../{ => work}/CleanCacheWorkerTest.java | 39 +++++++++++++++---- .../{ => work}/WorkManagerTestRule.java | 12 +++--- 2 files changed, 36 insertions(+), 15 deletions(-) rename app/src/androidTest/java/org/fdroid/fdroid/{ => work}/CleanCacheWorkerTest.java (58%) rename app/src/androidTest/java/org/fdroid/fdroid/{ => work}/WorkManagerTestRule.java (86%) diff --git a/app/src/androidTest/java/org/fdroid/fdroid/CleanCacheWorkerTest.java b/app/src/androidTest/java/org/fdroid/fdroid/work/CleanCacheWorkerTest.java similarity index 58% rename from app/src/androidTest/java/org/fdroid/fdroid/CleanCacheWorkerTest.java rename to app/src/androidTest/java/org/fdroid/fdroid/work/CleanCacheWorkerTest.java index e3706dc03..b57cd1034 100644 --- a/app/src/androidTest/java/org/fdroid/fdroid/CleanCacheWorkerTest.java +++ b/app/src/androidTest/java/org/fdroid/fdroid/work/CleanCacheWorkerTest.java @@ -1,25 +1,48 @@ -package org.fdroid.fdroid; +package org.fdroid.fdroid.work; import android.app.Instrumentation; - -import androidx.test.ext.junit.runners.AndroidJUnit4; +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.fdroid.fdroid.work.CleanCacheWorker; +import org.junit.Rule; import org.junit.Test; -import org.junit.runner.RunWith; 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; -@RunWith(AndroidJUnit4.class) +/** + * This test cannot run on Robolectric unfortunately since it does not support + *

+ * 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 = "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 = workManagerTestRule.workManager.getWorkInfoById(request.getId()); + assertEquals(WorkInfo.State.SUCCEEDED, workInfo.get().getState()); + } @Test public void testClearOldFiles() throws IOException, InterruptedException { diff --git a/app/src/androidTest/java/org/fdroid/fdroid/WorkManagerTestRule.java b/app/src/androidTest/java/org/fdroid/fdroid/work/WorkManagerTestRule.java similarity index 86% rename from app/src/androidTest/java/org/fdroid/fdroid/WorkManagerTestRule.java rename to app/src/androidTest/java/org/fdroid/fdroid/work/WorkManagerTestRule.java index a2091588b..fb6f6dc8e 100644 --- a/app/src/androidTest/java/org/fdroid/fdroid/WorkManagerTestRule.java +++ b/app/src/androidTest/java/org/fdroid/fdroid/work/WorkManagerTestRule.java @@ -1,23 +1,21 @@ -package org.fdroid.fdroid; +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 { - private Context targetContext; - private Context testContext; - private Configuration configuration; - private WorkManager workManager; + Context targetContext; + Context testContext; + Configuration configuration; + WorkManager workManager; @Override protected void starting(Description description) { From 9eba2430920310ca00bdd88403d73875bebb0e6b Mon Sep 17 00:00:00 2001 From: Hans-Christoph Steiner Date: Wed, 6 Jan 2021 16:12:47 +0100 Subject: [PATCH 7/8] use TAG to identify CleanCacheWorker to WorkManager TAG is already there, and it is meant to identify this class, and be unique. --- app/src/main/java/org/fdroid/fdroid/work/CleanCacheWorker.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/src/main/java/org/fdroid/fdroid/work/CleanCacheWorker.java b/app/src/main/java/org/fdroid/fdroid/work/CleanCacheWorker.java index e5ba902f4..c28f37925 100644 --- a/app/src/main/java/org/fdroid/fdroid/work/CleanCacheWorker.java +++ b/app/src/main/java/org/fdroid/fdroid/work/CleanCacheWorker.java @@ -52,8 +52,7 @@ public class CleanCacheWorker extends Worker { new PeriodicWorkRequest.Builder(CleanCacheWorker.class, interval, TimeUnit.MILLISECONDS) .setConstraints(constraintsBuilder.build()) .build(); - workManager.enqueueUniquePeriodicWork("clean_cache", - ExistingPeriodicWorkPolicy.REPLACE, cleanCache); + workManager.enqueueUniquePeriodicWork(TAG, ExistingPeriodicWorkPolicy.REPLACE, cleanCache); Utils.debugLog(TAG, "Scheduled periodic work for cleaning the cache."); } From 8f64b45e7942f618b85d8ddc612a6c5a253eef08 Mon Sep 17 00:00:00 2001 From: Hans-Christoph Steiner Date: Wed, 27 Jan 2021 14:55:38 +0100 Subject: [PATCH 8/8] gitlab-ci: fix excluding @LargeTest from emulator jobs fixup for !899 --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 5ef24f07b..34aa8c42d 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -82,7 +82,7 @@ errorprone: - adb devices - adb shell input keyevent 82 & - 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 - ./gradlew connectedFullDebugAndroidTest $FLAG || ./gradlew connectedFullDebugAndroidTest $FLAG