diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index ec966a21a2736438a0b7faa9d948c36443b78489..6f4937e1c2f5181ac9b6f98d423476f7691b8aa0 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -95,6 +95,14 @@ android:name=".services.ObserverService" android:enabled="true" /> + + + + + = MIN_BACKOFF_FOR_FETCHING_USERINFO + } +} \ No newline at end of file diff --git a/app/src/main/java/foundation/e/drive/services/InitializerService.java b/app/src/main/java/foundation/e/drive/services/InitializerService.java index 7492739990b54bd593eceb2747fd8acb6b3b70ec..1824c9ce7357f3b6b4f4aa01edc9ea7c56c3b683 100644 --- a/app/src/main/java/foundation/e/drive/services/InitializerService.java +++ b/app/src/main/java/foundation/e/drive/services/InitializerService.java @@ -109,7 +109,6 @@ public class InitializerService extends Service { */ private void start() { Timber.d("start()"); - CommonUtils.registerPeriodicUserInfoChecking(WorkManager.getInstance(this)); final List syncedFolders = RootSyncedFolderProvider.INSTANCE.getSyncedFolderRoots(getApplicationContext()); CommonUtils.registerInitializationWorkers(syncedFolders, WorkManager.getInstance(getApplicationContext()) ); diff --git a/app/src/main/java/foundation/e/drive/utils/AppConstants.java b/app/src/main/java/foundation/e/drive/utils/AppConstants.java index 1c9da75aee7f025396c2f1748e07479223997536..28214695c713b1eef6200bbb55f92a563ad57b14 100644 --- a/app/src/main/java/foundation/e/drive/utils/AppConstants.java +++ b/app/src/main/java/foundation/e/drive/utils/AppConstants.java @@ -9,10 +9,8 @@ package foundation.e.drive.utils; -import static com.owncloud.android.lib.resources.files.FileUtils.PATH_SEPARATOR; import android.os.Build; -import android.os.Environment; import java.text.SimpleDateFormat; import java.util.Locale; @@ -38,12 +36,12 @@ public abstract class AppConstants { public static final String ACCOUNT_DATA_USED_QUOTA_KEY = "used_quota"; public static final String ACCOUNT_DATA_TOTAL_QUOTA_KEY = "total_quota"; public static final String ACCOUNT_DATA_RELATIVE_QUOTA_KEY = "relative_quota"; + public static final String USER_INFO_LAST_FETCH_KEY = "userInfoLastCheck"; public static final String ACCOUNT_DATA_GROUPS = "group"; public static final String ACCOUNT_DATA_ALIAS_KEY = "alias"; public static final String ACCOUNT_DATA_EMAIL = "email"; public static final String ACCOUNT_USER_ID_KEY = "USERID"; public static final String[] MEDIA_SYNCABLE_CATEGORIES = new String[]{"Images", "Movies", "Music", "Ringtones", "Documents", "Podcasts"}; - public static final String[] SETTINGS_SYNCABLE_CATEGORIES = new String[]{"Rom settings"}; public static final String notificationChannelID = "foundation.e.drive"; public static final String WORK_GENERIC_TAG = "eDrive"; diff --git a/app/src/main/java/foundation/e/drive/utils/CommonUtils.java b/app/src/main/java/foundation/e/drive/utils/CommonUtils.java index 1ffb3a1ec3c21f81e9d30dd9377b22198e1cbc65..16556cb264052d5ed0fa1398e8524d9bfb94e62e 100644 --- a/app/src/main/java/foundation/e/drive/utils/CommonUtils.java +++ b/app/src/main/java/foundation/e/drive/utils/CommonUtils.java @@ -40,7 +40,6 @@ import java.util.List; import foundation.e.drive.R; import foundation.e.drive.models.SyncedFolder; -import foundation.e.drive.work.AccountUserInfoWorker; import foundation.e.drive.work.WorkRequestFactory; import timber.log.Timber; @@ -50,9 +49,7 @@ import static foundation.e.drive.utils.AppConstants.SETTINGSYNC_PROVIDER_AUTHORI import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.work.ExistingPeriodicWorkPolicy; import androidx.work.OneTimeWorkRequest; -import androidx.work.PeriodicWorkRequest; import androidx.work.WorkManager; @@ -123,7 +120,7 @@ public abstract class CommonUtils { * * @param account Account used for synchronisation * @param syncedFileStateIsMedia true if the concerned syncedFileState is a media's type element, false if it is a settings's type element - * @return + * @return true if sync is allowed in given condition */ public static boolean isThisSyncAllowed(@NonNull Account account, boolean syncedFileStateIsMedia) { return (syncedFileStateIsMedia && isMediaSyncEnabled(account)) @@ -153,14 +150,14 @@ public abstract class CommonUtils { /** * Read accountManager settings * - * @param account + * @param account Concerned account * @return true if usage of metered connection is allowed */ public static boolean isMeteredNetworkAllowed(@NonNull Account account) { return ContentResolver.getSyncAutomatically(account, METERED_NETWORK_ALLOWED_AUTHORITY); } - /** methods relative to file **/ + /* methods relative to file */ /** * Return name of a file from its access path @@ -188,14 +185,11 @@ public abstract class CommonUtils { final ConnectivityManager cm = context.getSystemService(ConnectivityManager.class); final NetworkCapabilities capabilities = cm.getNetworkCapabilities(cm.getActiveNetwork()); - if (capabilities != null + return capabilities != null && capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) && capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED) && (capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED) - || meteredNetworkAllowed)) { - return true; - } - return false; + || meteredNetworkAllowed); } @@ -229,13 +223,8 @@ public abstract class CommonUtils { MediaScannerConnection.scanFile(context, filePathArray, mimeType, - new MediaScannerConnection.OnScanCompletedListener() { - @Override - public void onScanCompleted(String path, Uri uri) { - Timber.tag("MediaScannerConnection") - .v("file %s was scanned with success: %s ", path, uri); - } - }); + (path, uri) -> Timber.tag("MediaScannerConnection") + .v("file %s was scanned with success: %s ", path, uri)); } /** @@ -258,7 +247,7 @@ public abstract class CommonUtils { /** * Formatter class is not used since bytes passed by server are in SI unit aka 1kb = 1024byte - * https://stackoverflow.com/questions/3758606/how-can-i-convert-byte-size-into-a-human-readable-format-in-java/3758880#3758880 + * ... * * @param bytes file/data size in bytes * @return String in human readable format @@ -342,11 +331,11 @@ public abstract class CommonUtils { /** * Job for Widget & notification about quota * - * @param workManager + * @param workManager WorkManager */ - public static void registerPeriodicUserInfoChecking(@NonNull WorkManager workManager) { - final PeriodicWorkRequest workRequest = WorkRequestFactory.getPeriodicWorkRequest(WorkRequestFactory.WorkType.PERIODIC_USER_INFO); - workManager.enqueueUniquePeriodicWork(AccountUserInfoWorker.UNIQUE_WORK_NAME, ExistingPeriodicWorkPolicy.REPLACE, workRequest); + public static void TriggerUserInfoFetching(@NonNull WorkManager workManager) { + final OneTimeWorkRequest getUserInfoRequest = WorkRequestFactory.getOneTimeWorkRequest(WorkRequestFactory.WorkType.ONE_TIME_USER_INFO, null); + workManager.enqueue(getUserInfoRequest); } /** @@ -368,4 +357,4 @@ public abstract class CommonUtils { } return value == null ? "" : value; } -} +} \ No newline at end of file diff --git a/app/src/main/java/foundation/e/drive/work/AccountUserInfoWorker.java b/app/src/main/java/foundation/e/drive/work/FetchUserInfoWorker.java similarity index 85% rename from app/src/main/java/foundation/e/drive/work/AccountUserInfoWorker.java rename to app/src/main/java/foundation/e/drive/work/FetchUserInfoWorker.java index 39b308c175dfccab88e7faeaec8a4356500aaa4f..8df8d3bdc635beea35a5af8262303422db680cf6 100644 --- a/app/src/main/java/foundation/e/drive/work/AccountUserInfoWorker.java +++ b/app/src/main/java/foundation/e/drive/work/FetchUserInfoWorker.java @@ -15,6 +15,7 @@ import static foundation.e.drive.utils.AppConstants.ACCOUNT_DATA_RELATIVE_QUOTA_ import static foundation.e.drive.utils.AppConstants.ACCOUNT_DATA_TOTAL_QUOTA_KEY; import static foundation.e.drive.utils.AppConstants.ACCOUNT_DATA_USED_QUOTA_KEY; import static foundation.e.drive.utils.AppConstants.ACCOUNT_USER_ID_KEY; +import static foundation.e.drive.utils.AppConstants.USER_INFO_LAST_FETCH_KEY; import android.accounts.Account; import android.accounts.AccountManager; @@ -51,18 +52,17 @@ import timber.log.Timber; * @author vincent Bourgmayer * @author TheScarastic */ -public class AccountUserInfoWorker extends Worker { - public static final String UNIQUE_WORK_NAME = "AccountUserInfoWorker"; +public class FetchUserInfoWorker extends Worker { private final AccountManager accountManager; private final GetUserInfoRemoteOperation GetUserInfoRemoteOperation = new GetUserInfoRemoteOperation(); - private final Context mContext; + private final Context context; private Account account; - public AccountUserInfoWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) { + public FetchUserInfoWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) { super(context, workerParams); - Timber.tag(AccountUserInfoWorker.class.getSimpleName()); - mContext = context; + Timber.tag(FetchUserInfoWorker.class.getSimpleName()); + this.context = context; accountManager = AccountManager.get(context); } @@ -71,21 +71,22 @@ public class AccountUserInfoWorker extends Worker { public Result doWork() { try { - account = CommonUtils.getAccount(mContext.getString(R.string.eelo_account_type), accountManager); + account = CommonUtils.getAccount(context.getString(R.string.eelo_account_type), accountManager); if (account == null) { return Result.failure(); } - final NextcloudClient client = DavClientProvider.getInstance().getNcClientInstance(account, mContext); + final NextcloudClient client = DavClientProvider.getInstance().getNcClientInstance(account, context); if (client != null) { if (fetchUserInfo(client) && fetchAliases()) { - Glide.with(mContext) + Glide.with(context) .load(client.getBaseUri() + AccountsActivity.NON_OFFICIAL_AVATAR_PATH + client.getUserId() + "/" + 300) .diskCacheStrategy(DiskCacheStrategy.ALL) .preload(); - ViewUtils.updateWidgetView(mContext); + ViewUtils.updateWidgetView(context); + saveLastExecutionTimestamp(context); return Result.success(); } else { return Result.retry(); @@ -109,7 +110,7 @@ public class AccountUserInfoWorker extends Worker { if (userId != null) { client.setUserId(userId); - AccountManager.get(mContext).setUserData(account, ACCOUNT_USER_ID_KEY, userId); + AccountManager.get(context).setUserData(account, ACCOUNT_USER_ID_KEY, userId); Timber.v("UserId %s saved for account", userId); } } @@ -166,19 +167,19 @@ public class AccountUserInfoWorker extends Worker { final String LAST_NOTIFICATION_TIMESTAMP_KEY = "last_notification_timestamp"; final long MILLI_SECONDS_IN_A_DAY = 24 * 60 * 60 * 1000; - SharedPreferences sharedPreferences = context.getSharedPreferences( + final SharedPreferences sharedPreferences = context.getSharedPreferences( AppConstants.SHARED_PREFERENCE_NAME, Context.MODE_PRIVATE ); - long previousTimestamp = sharedPreferences.getLong(LAST_NOTIFICATION_TIMESTAMP_KEY, 0); - long currentTimeStamp = System.currentTimeMillis(); + long previousTimeInMs = sharedPreferences.getLong(LAST_NOTIFICATION_TIMESTAMP_KEY, 0); + long currentTimeInMs = System.currentTimeMillis(); - if (previousTimestamp == 0 || - currentTimeStamp - previousTimestamp > MILLI_SECONDS_IN_A_DAY) { + if (previousTimeInMs == 0 || + currentTimeInMs - previousTimeInMs > MILLI_SECONDS_IN_A_DAY) { sharedPreferences .edit() - .putLong(LAST_NOTIFICATION_TIMESTAMP_KEY, currentTimeStamp) + .putLong(LAST_NOTIFICATION_TIMESTAMP_KEY, currentTimeInMs) .apply(); return true; } @@ -205,7 +206,7 @@ public class AccountUserInfoWorker extends Worker { } private boolean fetchAliases() { - final OwnCloudClient ocClient = DavClientProvider.getInstance().getClientInstance(account, mContext); + final OwnCloudClient ocClient = DavClientProvider.getInstance().getClientInstance(account, context); final String userId = accountManager.getUserData(account, ACCOUNT_USER_ID_KEY); if (userId == null || userId.isEmpty()) { @@ -213,6 +214,7 @@ public class AccountUserInfoWorker extends Worker { } final GetAliasOperation getAliasOperation = new GetAliasOperation(userId); + @SuppressWarnings("deprecation") final RemoteOperationResult> ocsResult = getAliasOperation.execute(ocClient); String aliases = ""; @@ -226,4 +228,11 @@ public class AccountUserInfoWorker extends Worker { Timber.d("fetchAliases(): success"); return true; } -} + + private void saveLastExecutionTimestamp(final Context context) { + context.getSharedPreferences( AppConstants.SHARED_PREFERENCE_NAME, Context.MODE_PRIVATE) + .edit() + .putLong(USER_INFO_LAST_FETCH_KEY, System.currentTimeMillis()) + .apply(); + } +} \ No newline at end of file diff --git a/app/src/main/java/foundation/e/drive/work/FirstStartWorker.java b/app/src/main/java/foundation/e/drive/work/FirstStartWorker.java index 7cdbf4f15c6eb94fde151eec34e1b7d64a148d60..6c8daf47e387a627735fac9d6c1db240b23e4a8e 100644 --- a/app/src/main/java/foundation/e/drive/work/FirstStartWorker.java +++ b/app/src/main/java/foundation/e/drive/work/FirstStartWorker.java @@ -10,7 +10,7 @@ package foundation.e.drive.work; import static foundation.e.drive.utils.AppConstants.INITIALFOLDERS_NUMBER; import static foundation.e.drive.work.WorkRequestFactory.WorkType.ONE_TIME_APP_LIST; -import static foundation.e.drive.work.WorkRequestFactory.WorkType.PERIODIC_SCAN; +import static foundation.e.drive.work.WorkRequestFactory.WorkType.ONE_TIME_USER_INFO; import android.content.Context; import android.content.Intent; @@ -52,7 +52,7 @@ public class FirstStartWorker extends Worker { .putInt(INITIALFOLDERS_NUMBER, 9) .apply(); - registerPeriodicWork(appContext); + registerWorker(appContext); getApplicationContext().startService(new Intent(getApplicationContext(), foundation.e.drive.services.SynchronizationService.class)); getApplicationContext().startService(new Intent(getApplicationContext(), foundation.e.drive.services.ObserverService.class)); @@ -68,11 +68,13 @@ public class FirstStartWorker extends Worker { workManager.enqueue(WorkRequestFactory.getOneTimeWorkRequest(ONE_TIME_APP_LIST, null)); } - private void registerPeriodicWork(@NonNull final Context context) { + private void registerWorker(@NonNull final Context context) { final WorkManager workManager = WorkManager.getInstance(context); + workManager.enqueue(WorkRequestFactory.getOneTimeWorkRequest(ONE_TIME_USER_INFO, null)); + workManager.enqueueUniquePeriodicWork(PeriodicWorker.UNIQUE_WORK_NAME, ExistingPeriodicWorkPolicy.KEEP, - WorkRequestFactory.getPeriodicWorkRequest(PERIODIC_SCAN)); + WorkRequestFactory.createPeriodicScanWorkRequest()); } } \ No newline at end of file diff --git a/app/src/main/java/foundation/e/drive/work/WorkRequestFactory.java b/app/src/main/java/foundation/e/drive/work/WorkRequestFactory.java index 6331f302873ee2089349408b903dac86cc22fa56..abee51af5dbbfdec26b26c440bb88cfd757b3af5 100644 --- a/app/src/main/java/foundation/e/drive/work/WorkRequestFactory.java +++ b/app/src/main/java/foundation/e/drive/work/WorkRequestFactory.java @@ -36,8 +36,6 @@ import foundation.e.drive.utils.AppConstants; public class WorkRequestFactory { public enum WorkType { - PERIODIC_USER_INFO, - PERIODIC_SCAN, ONE_TIME_FULL_SCAN, ONE_TIME_APP_LIST, ONE_TIME_USER_INFO, @@ -46,58 +44,22 @@ public class WorkRequestFactory { } /** - * Build an instance of PeriodicWorkRequest depending of the work type specified - * @param type WorkType. Should be FULL_SCAN or PERIODIC_USER_INFO or PERIODIC_APP_LIST - * If not, it will throw an InvalidParameterException - * @return Periodic WorkRequest - */ - @NonNull - public static PeriodicWorkRequest getPeriodicWorkRequest(@NonNull WorkType type) { - switch (type) { - case PERIODIC_SCAN: - return createPeriodicScanWorkRequest(); - case PERIODIC_USER_INFO: - return createPeriodicGetUserInfoWorkRequest(); - default: - throw new InvalidParameterException("Unsupported Work Type: " + type); - } - } - - /** - * Create a PeridocWorkRequest instance for + * Create a PeriodicWorkRequest instance for * a Full scan with constraints on network (should * be unmetered) and battery (shouldn't be low) * @return instance of PeriodicWorkRequest */ - private static PeriodicWorkRequest createPeriodicScanWorkRequest() { + @NonNull + public static PeriodicWorkRequest createPeriodicScanWorkRequest() { final Constraints constraints = createUnmeteredNetworkAndHighBatteryConstraints(); - final PeriodicWorkRequest workRequest = - new PeriodicWorkRequest.Builder(PeriodicWorker.class, - 26, TimeUnit.MINUTES, 5, TimeUnit.MINUTES) - .setConstraints(constraints) - .addTag(AppConstants.WORK_GENERIC_TAG) - .build(); - return workRequest; - } - - /** - * Create a periodic work request to get userInfo - * @return instance of PeriodicWorkRequest - */ - private static PeriodicWorkRequest createPeriodicGetUserInfoWorkRequest() { - final Constraints constraints = new Constraints.Builder() - .setRequiredNetworkType(NetworkType.CONNECTED) + return new PeriodicWorkRequest.Builder(PeriodicWorker.class, + 26, TimeUnit.MINUTES, 5, TimeUnit.MINUTES) + .setConstraints(constraints) + .addTag(AppConstants.WORK_GENERIC_TAG) .build(); - - final PeriodicWorkRequest workRequest = - new PeriodicWorkRequest.Builder(AccountUserInfoWorker.class, - 30, TimeUnit.MINUTES) - .addTag(AppConstants.WORK_GENERIC_TAG) - .setConstraints(constraints) - .build(); - return workRequest; } + /** * Build an instance of OneTimeWorkRequest depending of the work type specified. * @param type Should be ONE_TIME_USER_INFO, or FIRST_START, or CREATE_REMOTE_DIR @@ -161,9 +123,9 @@ public class WorkRequestFactory { private static OneTimeWorkRequest createOneTimeGetUserInfoWorkRequest() { final Constraints constraints = createUnmeteredNetworkAndHighBatteryConstraints(); - final OneTimeWorkRequest.Builder builder = new OneTimeWorkRequest.Builder(AccountUserInfoWorker.class); + final OneTimeWorkRequest.Builder builder = new OneTimeWorkRequest.Builder(FetchUserInfoWorker.class); - return builder.setBackoffCriteria(BackoffPolicy.LINEAR, 2, TimeUnit.MINUTES) + return builder.setBackoffCriteria(BackoffPolicy.LINEAR, 1, TimeUnit.MINUTES) .addTag(AppConstants.WORK_GENERIC_TAG) .addTag(AppConstants.WORK_INITIALIZATION_TAG) .setConstraints(constraints) @@ -179,7 +141,7 @@ public class WorkRequestFactory { private static OneTimeWorkRequest createOneTimeCreateRemoteFolderWorkRequest(@NonNull SyncedFolder syncedFolder) { final Constraints constraints = createUnmeteredNetworkAndHighBatteryConstraints(); - final OneTimeWorkRequest workRequest = new OneTimeWorkRequest.Builder( + return new OneTimeWorkRequest.Builder( CreateRemoteFolderWorker.class) .setBackoffCriteria(BackoffPolicy.LINEAR, 2, TimeUnit.MINUTES) .setInputData(createDataFromSyncedFolder(syncedFolder)) @@ -187,7 +149,6 @@ public class WorkRequestFactory { .addTag(AppConstants.WORK_INITIALIZATION_TAG) .setConstraints(constraints) .build(); - return workRequest; } /** @@ -196,12 +157,11 @@ public class WorkRequestFactory { * @return Instance of OneTimeWorkRequest */ private static OneTimeWorkRequest createOneTimeFirstStartWorkRequest() { - final OneTimeWorkRequest workRequest = new OneTimeWorkRequest.Builder(FirstStartWorker.class) + return new OneTimeWorkRequest.Builder(FirstStartWorker.class) .setBackoffCriteria(BackoffPolicy.LINEAR, 2, TimeUnit.MINUTES) .addTag(AppConstants.WORK_GENERIC_TAG) .addTag(AppConstants.WORK_INITIALIZATION_TAG) .build(); - return workRequest; } /** @@ -210,11 +170,10 @@ public class WorkRequestFactory { * @return instance of Constraints */ private static Constraints createUnmeteredNetworkAndHighBatteryConstraints() { - final Constraints constraint = new Constraints.Builder() + return new Constraints.Builder() .setRequiredNetworkType(NetworkType.UNMETERED) .setRequiresBatteryNotLow(true) .build(); - return constraint; } /** diff --git a/app/src/test/java/foundation/e/drive/receivers/ScreenOnReceiverTest.kt b/app/src/test/java/foundation/e/drive/receivers/ScreenOnReceiverTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..119edeacd7e6c940b6620c47279535f72ac703b1 --- /dev/null +++ b/app/src/test/java/foundation/e/drive/receivers/ScreenOnReceiverTest.kt @@ -0,0 +1,47 @@ +package foundation.e.drive.receivers + +import org.junit.Assert +import org.junit.Test + +internal class ScreenOnReceiverTest { + + private val currentTimeInMs = System.currentTimeMillis() + companion object { + val instanceUnderTest = ScreenOnReceiver() + } + + @Test + fun `canFetchUserInfo with 0 as previous check time`() { + val previousTimeInMs = 0L + val result = instanceUnderTest.canFetchUserInfo(previousTimeInMs, currentTimeInMs) + Assert.assertTrue("canFetchUserInfo should return true but returned: $result", result) + } + + @Test + fun `canFetchUserInfo with previous timeStamp earlier than min delay`() { + val previousTimeInMs = currentTimeInMs-1 + val result = instanceUnderTest.canFetchUserInfo(previousTimeInMs, currentTimeInMs) + Assert.assertFalse("canFetchUserInfo should return false but returned: $result", result) + } + + @Test + fun `canFetchUserInfo with previous timeStamp equal to min delay`() { + val previousTimeInMs = currentTimeInMs - ScreenOnReceiver.MIN_BACKOFF_FOR_FETCHING_USERINFO + val result = instanceUnderTest.canFetchUserInfo(previousTimeInMs, currentTimeInMs) + Assert.assertTrue("canFetchUserInfo should return true but returned: $result", result) + } + + @Test + fun `canFetchUserInfo with previous timeStamp older than min delay`() { + val previousTimeInMs = currentTimeInMs - ScreenOnReceiver.MIN_BACKOFF_FOR_FETCHING_USERINFO - 1 + val result = instanceUnderTest.canFetchUserInfo(previousTimeInMs, currentTimeInMs) + Assert.assertTrue("canFetchUserInfo should return true but returned: $result", result) + } + + @Test + fun `canFetchUserInfo with previous timeStamp bigger than current timestamp`() { + val previousTimeInMs = currentTimeInMs + 10000 + val result = instanceUnderTest.canFetchUserInfo(previousTimeInMs, currentTimeInMs) + Assert.assertFalse("canFetchUserInfo should return true but returned: $result", result) + } +} \ No newline at end of file