diff --git a/README.md b/README.md index 215c00516ec1bbb6658691bdc49f27dab178ea55..c7d430fa0f033ae1de1a4b494f92a45ed4cbf60f 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ eDrive can also receive some broadcast intent for different purprose: **Force the synchronization.** ```bash -adb shell am broadcast -a foundation.e.drive.action.FORCE_SYNC --receiver-include-background +adb shell am broadcast -a foundation.e.drive.action.FORCE_SCAN --receiver-include-background ``` **Generate a database dump accessible by the user** @@ -58,14 +58,52 @@ adb shell am broadcast -a foundation.e.drive.action.FORCE_SYNC --receiver-includ adb shell am broadcast -a foundation.e.drive.action.DUMP_DATABASE --receiver-include-background ``` +or Download database directly: + +```bash +adb pull /data/data/foundation.e.drive/databases +``` + **Disable log limit on release build** ```bash - adb shell am broadcast -a foundation.e.drive.action.FULL_LOG_ON_PROD --receiver-include-background --ez full_log_enable true +adb shell am broadcast -a foundation.e.drive.action.FULL_LOG_ON_PROD --receiver-include-background --ez full_log_enable true ``` **Limit log output on release build** *(after previous command)* ```bash - adb shell am broadcast -a foundation.e.drive.action.FULL_LOG_ON_PROD --receiver-include-background --ez full_log_enable false -``` \ No newline at end of file +adb shell am broadcast -a foundation.e.drive.action.FULL_LOG_ON_PROD --receiver-include-background --ez full_log_enable false +``` + +**Test SyncWorker: upload 10 empty files** + +Does not rely on FileObserver or FullScanWorker + +```bash +adb shell am broadcast -a foundation.e.drive.action.TEST_SYNC --receiver-include-background +``` + +**Display logat** + +```bash +adb logcat --pid=$(adb shell pidof -s foundation.e.drive) +``` + +You can also use the script `dev-tools.sh` to run those command. Use : `./dev-tools.sh -h` to display options + +### local NC for testing + +Use following documentation to set up a local NC instance + +https://gitlab.e.foundation/internal/wiki/-/wikis/qa-team/how-to-run-local-nextcloud + +`docker run -d -p 8080:80 -e NEXTCLOUD_TRUSTED_DOMAINS=":8080" -e SQLITE_DATABASE=nc -e NEXTCLOUD_ADMIN_USER=admin -e NEXTCLOUD_ADMIN_PASSWORD=admin nextcloud: +` + +Then you can use Wireshark by example to collect network packet. +Example of filter for wireshark: +`(ip.src == && ip.dst == ) || (ip.dst == && ip.src == ) && http` + + +*note: replace , and by values* \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index 2073fe130dac8b5b8f1dff13d0fce612110b6b55..eaff7661eaf0f7011e6933ec6f44d4f585ccd582 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -3,10 +3,9 @@ plugins { id 'org.jetbrains.kotlin.android' } - def versionMajor = 1 -def versionMinor = 3 -def versionPatch = 16 +def versionMinor = 4 +def versionPatch = 0 def getTestProp(String propName) { def result = "" @@ -97,16 +96,15 @@ dependencies { implementation 'com.github.nextcloud:android-library:2.13.0' implementation "commons-httpclient:commons-httpclient:3.1@jar" implementation fileTree(include: ['*.jar'], dir: 'libs') - api 'androidx.annotation:annotation:1.6.0' - implementation 'androidx.core:core:1.10.1' + api 'androidx.annotation:annotation:1.7.0' implementation 'androidx.appcompat:appcompat:1.6.1' implementation "androidx.constraintlayout:constraintlayout:2.1.4" - implementation 'com.google.android.material:material:1.9.0' + implementation 'com.google.android.material:material:1.10.0' implementation 'com.github.bumptech.glide:glide:4.15.1' implementation 'com.github.bumptech.glide:annotations:4.15.1' - implementation 'androidx.core:core-ktx:1.10.1' + implementation 'androidx.core:core-ktx:1.12.0' annotationProcessor 'com.github.bumptech.glide:compiler:4.15.1' - implementation "androidx.work:work-runtime:2.8.1" + implementation "androidx.work:work-runtime:2.9.0" implementation 'androidx.test:core:1.5.0' implementation 'com.jakewharton.timber:timber:5.0.1' implementation 'foundation.e:elib:0.0.1-alpha11' @@ -114,7 +112,7 @@ dependencies { androidTestImplementation 'androidx.test:runner:1.5.2' androidTestImplementation 'androidx.test:rules:1.5.0' - androidTestImplementation 'androidx.annotation:annotation:1.6.0' + androidTestImplementation 'androidx.annotation:annotation:1.7.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' androidTestImplementation 'junit:junit:4.13.2' @@ -124,5 +122,5 @@ dependencies { testImplementation 'junit:junit:4.13.2' testImplementation 'org.robolectric:robolectric:4.7.3' testImplementation 'org.mockito:mockito-core:5.0.0' - testImplementation 'androidx.work:work-testing:2.8.1' + testImplementation 'androidx.work:work-testing:2.9.0' } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index ac42e944d90f00c4a785e052e7b300a75e101e9f..e2caa58f4ad7b577fcfda369d3654a990adcf113 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -15,6 +15,11 @@ + + + + - + + diff --git a/app/src/main/java/foundation/e/drive/account/AccountUtils.kt b/app/src/main/java/foundation/e/drive/account/AccountUtils.kt new file mode 100644 index 0000000000000000000000000000000000000000..7d73f23a4c401762692a4516b1b30094836922ca --- /dev/null +++ b/app/src/main/java/foundation/e/drive/account/AccountUtils.kt @@ -0,0 +1,71 @@ +/* + * Copyright MURENA SAS 2022-2023 + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package foundation.e.drive.account + +import android.accounts.Account +import android.accounts.AccountManager +import android.content.Context +import foundation.e.drive.R +import foundation.e.drive.utils.AppConstants.SHARED_PREFERENCE_NAME +import foundation.e.drive.utils.AppConstants.ACCOUNT_DATA_GROUPS + +object AccountUtils { + + @JvmStatic + fun getPremiumPlan(accountManager: AccountManager, account: Account?): String? { + if (account == null) return null + val groupData = accountManager.getUserData(account, ACCOUNT_DATA_GROUPS) + val premiumGroup = extractPremiumGroup(groupData) + return extractPremiumPlan(premiumGroup) + } + + @JvmStatic + private fun extractPremiumPlan(premiumGroup: String?): String? { + if (premiumGroup.isNullOrEmpty()) return null + + val splitPremiumGroup = premiumGroup.split("-") + return if (splitPremiumGroup.size < 2) null + else splitPremiumGroup[1] + } + + @JvmStatic + private fun extractPremiumGroup(groupData: String?): String? { + if (groupData.isNullOrEmpty()) return null + + val groups = groupData.split(",") + return groups.firstOrNull { group: String -> group.contains("premium-") } + } + + @JvmStatic + fun getAccount(context: Context): Account? { + val prefs = context.getSharedPreferences(SHARED_PREFERENCE_NAME, Context.MODE_PRIVATE) + val accountName = prefs.getString(AccountManager.KEY_ACCOUNT_NAME, "") + + return getAccount(accountName!!, context) + } + + @JvmStatic + fun getAccount(accountName: String, context: Context): Account? { + if (accountName.isEmpty()) return null + + val accountManager = AccountManager.get(context) + val accountType = context.getString(R.string.eelo_account_type) + + return accountManager.getAccountsByType(accountType) + .firstOrNull { account -> account.name == accountName } + } +} diff --git a/app/src/main/java/foundation/e/drive/account/receivers/AccountAddedReceiver.kt b/app/src/main/java/foundation/e/drive/account/receivers/AccountAddedReceiver.kt index 4e0b92480f0ba5c34fcf37853ab392e470b447e8..2d0eb8d950f09f780d6945a3ed6ac62965e0b0b5 100644 --- a/app/src/main/java/foundation/e/drive/account/receivers/AccountAddedReceiver.kt +++ b/app/src/main/java/foundation/e/drive/account/receivers/AccountAddedReceiver.kt @@ -15,12 +15,13 @@ import android.content.SharedPreferences import androidx.work.OneTimeWorkRequest import androidx.work.WorkManager import foundation.e.drive.R +import foundation.e.drive.account.AccountUtils import foundation.e.drive.models.SyncedFolder import foundation.e.drive.utils.AppConstants -import foundation.e.drive.utils.CommonUtils import foundation.e.drive.utils.DavClientProvider import foundation.e.drive.utils.RootSyncedFolderProvider import foundation.e.drive.work.WorkRequestFactory.* +import foundation.e.drive.work.WorkerUtils import timber.log.Timber /** @@ -51,7 +52,7 @@ class AccountAddedReceiver() : BroadcastReceiver() { if (registerSetupWorkers(context)) { DavClientProvider.getInstance().cleanUp() - CommonUtils.registerPeriodicUserInfoChecking(WorkManager.getInstance(context)) + WorkerUtils.enqueuePeriodicUserInfoFetching(WorkManager.getInstance(context)) } } @@ -80,7 +81,7 @@ class AccountAddedReceiver() : BroadcastReceiver() { return false } - if (!isExistingAccount(accountName, accountType, context)) { + if (!isExistingAccount(accountName, context)) { Timber.w("No account exist for username: %s ", accountType, accountName) return false } @@ -96,9 +97,8 @@ class AccountAddedReceiver() : BroadcastReceiver() { return prefs.getBoolean(AppConstants.SETUP_COMPLETED, false) } - private fun isExistingAccount(accountName: String, accountType: String, context: Context): Boolean { - val account = CommonUtils.getAccount(accountName, accountType, AccountManager.get(context)) - return account != null + private fun isExistingAccount(accountName: String, context: Context): Boolean { + return AccountUtils.getAccount(accountName, context) != null } private fun registerSetupWorkers(context: Context): Boolean { @@ -118,7 +118,7 @@ class AccountAddedReceiver() : BroadcastReceiver() { val rootSyncedFolderList: List = RootSyncedFolderProvider.getSyncedFolderRoots(context) - if (rootSyncedFolderList.isNullOrEmpty()) { + if (rootSyncedFolderList.isEmpty()) { return null } diff --git a/app/src/main/java/foundation/e/drive/account/setup/FinishSetupWorker.java b/app/src/main/java/foundation/e/drive/account/setup/FinishSetupWorker.java index 1563cf01cbf0b2af40f4bfb9af174522f328840d..3a8479739d8155e47e2bc4b09cf1a854547a6606 100644 --- a/app/src/main/java/foundation/e/drive/account/setup/FinishSetupWorker.java +++ b/app/src/main/java/foundation/e/drive/account/setup/FinishSetupWorker.java @@ -11,23 +11,16 @@ package foundation.e.drive.account.setup; import static android.content.Context.MODE_PRIVATE; import static foundation.e.drive.utils.AppConstants.INITIAL_FOLDER_NUMBER; import static foundation.e.drive.utils.AppConstants.SHARED_PREFERENCE_NAME; -import static foundation.e.drive.work.WorkRequestFactory.WorkType.ONE_TIME_APP_LIST; -import static foundation.e.drive.work.WorkRequestFactory.WorkType.ONE_TIME_FORCED_FULL_SCAN; -import static foundation.e.drive.work.WorkRequestFactory.WorkType.PERIODIC_SCAN; import android.content.Context; import androidx.annotation.NonNull; -import androidx.work.ExistingPeriodicWorkPolicy; -import androidx.work.ExistingWorkPolicy; import androidx.work.WorkManager; import androidx.work.Worker; import androidx.work.WorkerParameters; -import foundation.e.drive.periodicScan.FullScanWorker; -import foundation.e.drive.periodicScan.PeriodicScanWorker; import foundation.e.drive.utils.AppConstants; -import foundation.e.drive.work.WorkRequestFactory; +import foundation.e.drive.work.WorkerUtils; import timber.log.Timber; /** @@ -68,26 +61,8 @@ public class FinishSetupWorker extends Worker { private void enqueueWorkers(@NonNull final Context appContext) { final WorkManager workManager = WorkManager.getInstance(appContext); - enqueueAppListGenerationWorker(workManager); - enqueueFullScanWorker(workManager); - enqueuePeriodicFileScanWorker(workManager); - } - - private void enqueueFullScanWorker(@NonNull final WorkManager workManager) { - workManager.enqueueUniqueWork( - FullScanWorker.UNIQUE_WORK_NAME, - ExistingWorkPolicy.KEEP, - WorkRequestFactory.getOneTimeWorkRequest(ONE_TIME_FORCED_FULL_SCAN, null) - ); - } - - private void enqueueAppListGenerationWorker(@NonNull final WorkManager workManager) { - workManager.enqueue(WorkRequestFactory.getOneTimeWorkRequest(ONE_TIME_APP_LIST, null)); - } - - private void enqueuePeriodicFileScanWorker(@NonNull final WorkManager workManager) { - workManager.enqueueUniquePeriodicWork(PeriodicScanWorker.UNIQUE_WORK_NAME, - ExistingPeriodicWorkPolicy.KEEP, - WorkRequestFactory.getPeriodicWorkRequest(PERIODIC_SCAN)); + WorkerUtils.enqueueOneTimeAppListGenerator(workManager); + WorkerUtils.enqueueOneTimeFullScan(workManager, false); + WorkerUtils.enqueuePeriodicFullScan(workManager); } } \ No newline at end of file diff --git a/app/src/main/java/foundation/e/drive/account/setup/RootFolderSetupWorker.java b/app/src/main/java/foundation/e/drive/account/setup/RootFolderSetupWorker.java index 6488eeb54c74b1c487105bf1090d945cf72f8d29..99082e5e83a606456ab0946e83c379d9af38857c 100644 --- a/app/src/main/java/foundation/e/drive/account/setup/RootFolderSetupWorker.java +++ b/app/src/main/java/foundation/e/drive/account/setup/RootFolderSetupWorker.java @@ -9,9 +9,7 @@ package foundation.e.drive.account.setup; import android.accounts.Account; -import android.accounts.AccountManager; import android.content.Context; -import android.content.SharedPreferences; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -25,10 +23,9 @@ import com.owncloud.android.lib.resources.files.CreateFolderRemoteOperation; import java.io.File; -import foundation.e.drive.R; +import foundation.e.drive.account.AccountUtils; import foundation.e.drive.database.DbHelper; import foundation.e.drive.models.SyncedFolder; -import foundation.e.drive.utils.AppConstants; import foundation.e.drive.utils.CommonUtils; import foundation.e.drive.utils.DavClientProvider; import timber.log.Timber; @@ -62,7 +59,7 @@ public class RootFolderSetupWorker extends Worker { public Result doWork() { try { final Context context = getApplicationContext(); - final Account account = getAccount(); + final Account account = AccountUtils.getAccount(getApplicationContext()); if (account == null) { Timber.d("doWork(): Can't get valid account"); return Result.failure(); @@ -129,14 +126,4 @@ public class RootFolderSetupWorker extends Worker { return result; } - - @Nullable - private Account getAccount() { - final SharedPreferences prefs = getApplicationContext().getSharedPreferences(AppConstants.SHARED_PREFERENCE_NAME, Context.MODE_PRIVATE); - final String accountName = prefs.getString(AccountManager.KEY_ACCOUNT_NAME, ""); - final String accountType = getApplicationContext().getString(R.string.eelo_account_type); - - if (accountName.isEmpty()) return null; - return CommonUtils.getAccount(accountName, accountType, AccountManager.get(getApplicationContext())); - } } diff --git a/app/src/main/java/foundation/e/drive/activity/AccountsActivity.java b/app/src/main/java/foundation/e/drive/activity/AccountsActivity.java index 5929f3709885d120e5dbd7b2226bcb5b0ce0fb46..a0aea0e5386d8436104d5cee6fffae528491b917 100644 --- a/app/src/main/java/foundation/e/drive/activity/AccountsActivity.java +++ b/app/src/main/java/foundation/e/drive/activity/AccountsActivity.java @@ -33,8 +33,8 @@ import com.bumptech.glide.Glide; import com.owncloud.android.lib.common.OwnCloudClient; import foundation.e.drive.R; +import foundation.e.drive.account.AccountUtils; import foundation.e.drive.databinding.ActivityAccountsBinding; -import foundation.e.drive.utils.AccountUtils; import foundation.e.drive.utils.CommonUtils; import foundation.e.drive.utils.DavClientProvider; import foundation.e.drive.widgets.EDriveWidget; @@ -113,9 +113,10 @@ public class AccountsActivity extends AppCompatActivity { binding.plan.setText(getString(R.string.free_plan, totalShownQuota)); - AccountUtils.getPremiumGroup(accountManager, account) - .ifPresent(group -> binding.plan.setText(getString(R.string.premium_plan, - group.split("-")[1]))); + final String premiumPlan = AccountUtils.getPremiumPlan(accountManager, account); + if (premiumPlan != null) { + binding.plan.setText(getString(R.string.premium_plan, premiumPlan)); + } binding.myPlan.setVisibility(View.VISIBLE); binding.plan.setVisibility(View.VISIBLE); diff --git a/app/src/main/java/foundation/e/drive/database/DbHelper.java b/app/src/main/java/foundation/e/drive/database/DbHelper.java index a71ca586d39877c9fc988688d0465bdbe8ad5498..5a4ac077c73a99ca5bee72b714824e90ba6b850c 100644 --- a/app/src/main/java/foundation/e/drive/database/DbHelper.java +++ b/app/src/main/java/foundation/e/drive/database/DbHelper.java @@ -47,7 +47,6 @@ public final class DbHelper extends SQLiteOpenHelper { */ public DbHelper(@NonNull Context context){ super(context, DATABASE_NAME, null, DATABASE_VERSION); - Timber.tag(DbHelper.class.getSimpleName()); } /** diff --git a/app/src/main/java/foundation/e/drive/periodicScan/FullScanWorker.kt b/app/src/main/java/foundation/e/drive/periodicScan/FullScanWorker.kt index 49cc9c533e850a707300423842e9db091c528273..855f71babea8e31c98bd6c125244aa0599e0a04b 100644 --- a/app/src/main/java/foundation/e/drive/periodicScan/FullScanWorker.kt +++ b/app/src/main/java/foundation/e/drive/periodicScan/FullScanWorker.kt @@ -8,7 +8,6 @@ package foundation.e.drive.periodicScan import android.accounts.Account -import android.accounts.AccountManager import android.app.Application import android.content.Context import android.content.SharedPreferences @@ -16,7 +15,7 @@ import androidx.work.Worker import androidx.work.WorkerParameters import com.owncloud.android.lib.common.operations.RemoteOperationResult import com.owncloud.android.lib.resources.files.model.RemoteFile -import foundation.e.drive.R +import foundation.e.drive.account.AccountUtils import foundation.e.drive.database.DbHelper import foundation.e.drive.models.SyncRequest import foundation.e.drive.models.SyncedFolder @@ -51,7 +50,7 @@ class FullScanWorker(private val context: Context, private val workerParams: Wor AppConstants.SHARED_PREFERENCE_NAME, Context.MODE_PRIVATE ) - val account = loadAccount(prefs) + val account = AccountUtils.getAccount(applicationContext) val startAllowed = checkStartConditions(account, prefs, requestCollector) if (!startAllowed) { @@ -115,13 +114,6 @@ class FullScanWorker(private val context: Context, private val workerParams: Wor return true } - private fun loadAccount(prefs: SharedPreferences): Account? { - val accountName = prefs.getString(AccountManager.KEY_ACCOUNT_NAME, "") ?: return null - val accountType = context.getString(R.string.eelo_account_type) - - return CommonUtils.getAccount(accountName, accountType, AccountManager.get(context)) - } - /** * indicate if minimum delay between two periodic scan is respected */ diff --git a/app/src/main/java/foundation/e/drive/receivers/BootCompletedReceiver.java b/app/src/main/java/foundation/e/drive/receivers/BootCompletedReceiver.java index 1f67e5a741a0cd27d5c80d4dc578cef9b55d5569..ae1c037bccc532a7994f2ac58f4ddef7284f0ba7 100644 --- a/app/src/main/java/foundation/e/drive/receivers/BootCompletedReceiver.java +++ b/app/src/main/java/foundation/e/drive/receivers/BootCompletedReceiver.java @@ -16,12 +16,14 @@ import android.content.SharedPreferences; import android.database.sqlite.SQLiteException; import androidx.annotation.NonNull; +import androidx.work.WorkManager; import foundation.e.drive.BuildConfig; import foundation.e.drive.database.DbHelper; import foundation.e.drive.synchronization.SyncProxy; import foundation.e.drive.utils.AppConstants; import foundation.e.drive.utils.CommonUtils; +import foundation.e.drive.work.WorkerUtils; import timber.log.Timber; /** @@ -31,27 +33,31 @@ import timber.log.Timber; public class BootCompletedReceiver extends BroadcastReceiver { private static final String DATE_SYSTEM_PROPERTY = "ro.build.date"; private static final String PREF_VERSION_CODE = "VERSION_CODE"; + private static final String OLD_SETUP_COMPLETED_PREF_KEY ="initService_has_run"; + + private static final int VERSION_CODE_FOR_UPDATE_1 = 1002000; + private static final int VERSION_CODE_FOR_UPDATE_2 = 1003017; + @Override public void onReceive(@NonNull Context context, @NonNull Intent intent) { final String action = intent.getAction(); - Timber.tag(BootCompletedReceiver.class.getSimpleName()).v("onReceive(...)"); - final SharedPreferences pref = context.getSharedPreferences(AppConstants.SHARED_PREFERENCE_NAME, Context.MODE_PRIVATE); + Timber.v("onReceive(...)"); + final SharedPreferences prefs = context.getSharedPreferences(AppConstants.SHARED_PREFERENCE_NAME, Context.MODE_PRIVATE); if (Intent.ACTION_BOOT_COMPLETED.equals(action)) { final String currentDateProp = CommonUtils.getProp(DATE_SYSTEM_PROPERTY); - if (isOsUpdated(pref, currentDateProp)) { // App is persistent so can only be updated (by replacement) on OS update + if (isOsUpdated(prefs, currentDateProp)) { // App is persistent so can only be updated (by replacement) on OS update handleOsUpdate(context); } + changeSetupCompletedPreferenceKey(prefs); + + if (!isSetupCompleted(prefs)) return; - if (pref.getBoolean(AppConstants.SETUP_COMPLETED, false) - && BuildConfig.VERSION_CODE > pref.getInt(PREF_VERSION_CODE, 1002000)) { - pref.edit().putInt(PREF_VERSION_CODE, BuildConfig.VERSION_CODE).apply(); - try { - DbHelper.cleanSyncedFileStateTableAfterUpdate(context); - } catch (SQLiteException exception) { - Timber.e(exception); - } + final int lastSavedVersionCode = prefs.getInt(PREF_VERSION_CODE, VERSION_CODE_FOR_UPDATE_1); + if (isEdriveUpdated(lastSavedVersionCode)) { + onEdriveUpdate(lastSavedVersionCode, context); + prefs.edit().putInt(PREF_VERSION_CODE, BuildConfig.VERSION_CODE).apply(); } SyncProxy.INSTANCE.startListeningFiles((Application) context.getApplicationContext()); @@ -60,20 +66,60 @@ public class BootCompletedReceiver extends BroadcastReceiver { private void forceDBUpdate(@NonNull Context context) { final DbHelper dbHelper = new DbHelper(context); - dbHelper.getWritableDatabase().close(); //Force upgrade of db. + dbHelper.getWritableDatabase().close(); + } + + private void changeSetupCompletedPreferenceKey(@NonNull SharedPreferences prefs) { + if (prefs.getBoolean(OLD_SETUP_COMPLETED_PREF_KEY, false)) { + Timber.i("Update setup complete preferences"); + prefs + .edit() + .remove(OLD_SETUP_COMPLETED_PREF_KEY) + .putBoolean(AppConstants.SETUP_COMPLETED, true) + .apply(); + + } } /** * Force reinitialization, upgrade of DB in case of OS update - * todo remove when setPersistentFlag=true will be removed * @param context Context used to start InitializationService */ private void handleOsUpdate(@NonNull Context context) { forceDBUpdate(context); } + /** @noinspection SpellCheckingInspection*/ + private boolean isEdriveUpdated(int oldVersionCode) { + Timber.d("isEdriveUpdated (%s > %s) ?", BuildConfig.VERSION_CODE, oldVersionCode); + return BuildConfig.VERSION_CODE > oldVersionCode; + } + + private boolean isSetupCompleted(@NonNull SharedPreferences prefs) { + return prefs.getBoolean(AppConstants.SETUP_COMPLETED, false); + } + + private void onEdriveUpdate(int oldVersionCode, @NonNull Context context) { + if (oldVersionCode <= VERSION_CODE_FOR_UPDATE_1) { + try { + DbHelper.cleanSyncedFileStateTableAfterUpdate(context); + } catch (SQLiteException exception) { + Timber.e(exception); + } + } + + if (oldVersionCode <= VERSION_CODE_FOR_UPDATE_2) { + Timber.d("Triggered the update 2 from: %s", oldVersionCode); + final WorkManager workManager= WorkManager.getInstance(context); + workManager.cancelAllWork(); + + WorkerUtils.enqueuePeriodicFullScan(workManager); + WorkerUtils.enqueuePeriodicUserInfoFetching(workManager); + } + } + private boolean isOsUpdated(@NonNull SharedPreferences prefs, @NonNull String currentDateProp) { final String lastKnownDateProp = prefs.getString(DATE_SYSTEM_PROPERTY, ""); return !currentDateProp.equals(lastKnownDateProp); } -} +} \ No newline at end of file diff --git a/app/src/main/java/foundation/e/drive/receivers/DebugCmdReceiver.java b/app/src/main/java/foundation/e/drive/receivers/DebugCmdReceiver.java index b4b6cf2956e2b002cc6f0c56f96b7809ab332c88..d1c743c2da55a576745cd1946dfd9c01a1f822c3 100644 --- a/app/src/main/java/foundation/e/drive/receivers/DebugCmdReceiver.java +++ b/app/src/main/java/foundation/e/drive/receivers/DebugCmdReceiver.java @@ -7,21 +7,28 @@ */ package foundation.e.drive.receivers; -import static foundation.e.drive.work.WorkRequestFactory.WorkType.ONE_TIME_FORCED_FULL_SCAN; - import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; +import android.os.Environment; import androidx.annotation.NonNull; -import androidx.work.ExistingWorkPolicy; -import androidx.work.OneTimeWorkRequest; import androidx.work.WorkManager; +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import foundation.e.drive.EdriveApplication; import foundation.e.drive.database.DbHelper; -import foundation.e.drive.periodicScan.FullScanWorker; +import foundation.e.drive.models.SyncRequest; +import foundation.e.drive.models.SyncedFileState; +import foundation.e.drive.models.SyncedFolder; +import foundation.e.drive.synchronization.SyncProxy; +import foundation.e.drive.synchronization.SyncRequestCollector; import foundation.e.drive.utils.ReleaseTree; -import foundation.e.drive.work.WorkRequestFactory; +import foundation.e.drive.work.WorkerUtils; import timber.log.Timber; /** @@ -30,21 +37,21 @@ import timber.log.Timber; */ public class DebugCmdReceiver extends BroadcastReceiver { - public static final String ACTION_FORCE_SYNC = "foundation.e.drive.action.FORCE_SYNC"; + public static final String ACTION_TEST_SYNC ="foundation.e.drive.action.TEST_SYNC"; + public static final String ACTION_FORCE_SCAN = "foundation.e.drive.action.FORCE_SCAN"; public static final String ACTION_DUMP_DATABASE = "foundation.e.drive.action.DUMP_DATABASE"; public static final String ACTION_FULL_LOG_ON_PROD = "foundation.e.drive.action.FULL_LOG_ON_PROD"; private static final String FULL_LOG_ENABLE_KEY = "full_log_enable"; + @Override public void onReceive(@NonNull Context context, @NonNull Intent intent) { Timber.tag(DebugCmdReceiver.class.getSimpleName()).v("onReceive"); + final WorkManager workManager = WorkManager.getInstance(context); + switch (intent.getAction()) { - case ACTION_FORCE_SYNC: + case ACTION_FORCE_SCAN: Timber.d("Force Sync intent received"); - final WorkManager workManager = WorkManager.getInstance(context); - final OneTimeWorkRequest fullScanWorkRequest = WorkRequestFactory.getOneTimeWorkRequest(ONE_TIME_FORCED_FULL_SCAN, null); - workManager.enqueueUniqueWork(FullScanWorker.UNIQUE_WORK_NAME, - ExistingWorkPolicy.KEEP, - fullScanWorkRequest); + WorkerUtils.enqueueOneTimeFullScan(workManager, true); break; case ACTION_DUMP_DATABASE: Timber.d("Dump database intent received"); @@ -55,8 +62,51 @@ public class DebugCmdReceiver extends BroadcastReceiver { ReleaseTree.allowDebugLogOnProd(allow_full_log); Timber.d("Allow full log on prod: %s", allow_full_log); break; + case ACTION_TEST_SYNC: + Timber.d("Test SyncWorker.kt started"); + final SyncRequestCollector collector = (SyncRequestCollector) SyncProxy.INSTANCE; + collector.onPeriodicScanStart((EdriveApplication) context.getApplicationContext()); + + final File dir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS); + final List requests = generateDummyFileToSync(dir, context); + + if (requests.isEmpty()) return; + + collector.queueSyncRequests(requests, context.getApplicationContext()); + + collector.startSynchronization(context.getApplicationContext()); + break; default: break; } } + + private List generateDummyFileToSync(File dir, Context context) { + final List result = new ArrayList<>(); + final int fileAmount = 10; + final SyncedFolder parentFolder = DbHelper.getSyncedFolderByLocalPath(dir.getAbsolutePath(), context); + + if (parentFolder == null || !parentFolder.isEnabled()) { + Timber.d("Won't send sync request: no parent are known for n %s", dir.getAbsolutePath()); + return result; + } + + for (int index = 0; index < fileAmount; index++) { + final String fileName = "a" + System.currentTimeMillis()/1200 + ".txt"; + File file = new File(dir.getAbsolutePath(), fileName); + try { + file.createNewFile(); + } catch (IOException exception) { + Timber.d("can't create file"); + continue; + } + + final String remotePath = parentFolder.getRemoteFolder() + fileName; + final SyncedFileState fileState = new SyncedFileState(-1, fileName, file.getAbsolutePath(), remotePath, "", 0L, parentFolder.getId(), true, 3); + + final SyncRequest request = new SyncRequest(fileState, SyncRequest.Type.UPLOAD); + result.add(request); + } + return result; + } } \ No newline at end of file diff --git a/app/src/main/java/foundation/e/drive/synchronization/SyncProxy.kt b/app/src/main/java/foundation/e/drive/synchronization/SyncProxy.kt index ef829bda64d950871f3351b844bf423f914da960..bd2df4301e71e6c5e33e009fd258e18e41442893 100644 --- a/app/src/main/java/foundation/e/drive/synchronization/SyncProxy.kt +++ b/app/src/main/java/foundation/e/drive/synchronization/SyncProxy.kt @@ -9,14 +9,12 @@ package foundation.e.drive.synchronization import android.app.Application import android.content.Context -import androidx.work.ExistingWorkPolicy import androidx.work.WorkManager import foundation.e.drive.EdriveApplication import foundation.e.drive.database.FailedSyncPrefsManager import foundation.e.drive.models.SyncRequest import foundation.e.drive.models.SyncWrapper -import foundation.e.drive.work.WorkRequestFactory -import foundation.e.drive.work.WorkRequestFactory.WorkType.ONE_TIME_SYNC +import foundation.e.drive.work.WorkerUtils import timber.log.Timber import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentLinkedQueue @@ -115,11 +113,13 @@ object SyncProxy: SyncRequestCollector, SyncManager { override fun startSynchronization(context: Context) { if (context !is EdriveApplication) { Timber.d("Invalid parameter: startSynchronization(context)") + StateMachine.changeState(SyncState.LISTENING_FILES) return } if (syncRequestQueue.isEmpty()) { Timber.d("Request queue is empty") + StateMachine.changeState(SyncState.LISTENING_FILES) return } @@ -133,10 +133,7 @@ object SyncProxy: SyncRequestCollector, SyncManager { } if (previousSyncState != SyncState.SYNCHRONIZING) { - val workManager = WorkManager.getInstance(context) - val workRequest = WorkRequestFactory.getOneTimeWorkRequest(ONE_TIME_SYNC, null) - - workManager.enqueueUniqueWork(SyncWorker.UNIQUE_WORK_NAME, ExistingWorkPolicy.KEEP, workRequest) + WorkerUtils.enqueueOneTimeSync(WorkManager.getInstance(context)) } } diff --git a/app/src/main/java/foundation/e/drive/synchronization/SyncTask.kt b/app/src/main/java/foundation/e/drive/synchronization/SyncTask.kt index d37317e846da5cc1d6433ff03a50f7a35a58ec43..497d547b89a196712b4629ed5b939b24df0aa1c0 100644 --- a/app/src/main/java/foundation/e/drive/synchronization/SyncTask.kt +++ b/app/src/main/java/foundation/e/drive/synchronization/SyncTask.kt @@ -17,7 +17,9 @@ import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCo import foundation.e.drive.database.DbHelper import foundation.e.drive.database.FailedSyncPrefsManager import foundation.e.drive.models.SyncRequest -import foundation.e.drive.models.SyncRequest.Type.* +import foundation.e.drive.models.SyncRequest.Type.UPLOAD +import foundation.e.drive.models.SyncRequest.Type.DISABLE_SYNCING +import foundation.e.drive.models.SyncRequest.Type.DOWNLOAD import foundation.e.drive.models.SyncWrapper import foundation.e.drive.synchronization.tasks.UploadFileOperation import foundation.e.drive.utils.CommonUtils @@ -143,7 +145,7 @@ class SyncTask( if (!success) { if (request.operationType == SyncRequest.Type.UPLOAD) { val filePath = fileState.localPath - if (filePath.isEmpty()) return; + if (filePath.isEmpty()) return val file = File(filePath) if (file.length() >= UploadFileOperation.FILE_SIZE_FLOOR_FOR_CHUNKED) return } diff --git a/app/src/main/java/foundation/e/drive/synchronization/SyncWorker.kt b/app/src/main/java/foundation/e/drive/synchronization/SyncWorker.kt index eda410b26725e604426f23f46127c62c2224f48e..861d7b7cfef126e7535c9eeb6b292a8261e7dae4 100644 --- a/app/src/main/java/foundation/e/drive/synchronization/SyncWorker.kt +++ b/app/src/main/java/foundation/e/drive/synchronization/SyncWorker.kt @@ -8,11 +8,12 @@ package foundation.e.drive.synchronization import android.accounts.Account -import android.accounts.AccountManager import android.app.NotificationChannel import android.app.NotificationManager import android.content.Context import android.content.Context.NOTIFICATION_SERVICE +import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC +import android.os.Build import androidx.core.app.NotificationCompat import androidx.work.ForegroundInfo @@ -21,8 +22,7 @@ import androidx.work.WorkerParameters import com.owncloud.android.lib.common.OwnCloudClient import foundation.e.drive.EdriveApplication import foundation.e.drive.R -import foundation.e.drive.utils.AppConstants -import foundation.e.drive.utils.CommonUtils +import foundation.e.drive.account.AccountUtils import foundation.e.drive.utils.DavClientProvider import timber.log.Timber import java.util.concurrent.Executors @@ -57,7 +57,7 @@ class SyncWorker( override fun doWork(): Result { try { - account = loadAccount() + account = AccountUtils.getAccount(applicationContext) if (account == null) { Timber.d("Warning : account is null") syncManager.startListeningFiles(applicationContext as EdriveApplication) @@ -87,15 +87,6 @@ class SyncWorker( return Result.success() } - private fun loadAccount(): Account? { - val context = applicationContext - val prefs = context.getSharedPreferences(AppConstants.SHARED_PREFERENCE_NAME, Context.MODE_PRIVATE) - val accountName = prefs.getString(AccountManager.KEY_ACCOUNT_NAME, "") - val accountType = context.getString(R.string.eelo_account_type) - //for above: AS complaint about accountName as String? while it can't be... - return CommonUtils.getAccount(accountName!!, accountType, AccountManager.get(context)) - } - private fun getOcClient(account: Account): OwnCloudClient? { return DavClientProvider.getInstance().getClientInstance( account, @@ -144,7 +135,11 @@ class SyncWorker( .build() - return ForegroundInfo(NOTIFICATION_ID, notification) + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + ForegroundInfo(NOTIFICATION_ID, notification, FOREGROUND_SERVICE_TYPE_DATA_SYNC) + } else { + ForegroundInfo(NOTIFICATION_ID, notification) + } } private fun createNotificationChannel() { diff --git a/app/src/main/java/foundation/e/drive/synchronization/tasks/UploadFileOperation.java b/app/src/main/java/foundation/e/drive/synchronization/tasks/UploadFileOperation.java index e141198c227332893d809b1071251254de005a99..d86508bc40cc3c7e5b9a43ee216d90b48980a48b 100644 --- a/app/src/main/java/foundation/e/drive/synchronization/tasks/UploadFileOperation.java +++ b/app/src/main/java/foundation/e/drive/synchronization/tasks/UploadFileOperation.java @@ -99,13 +99,13 @@ public class UploadFileOperation extends RemoteOperation { final ResultCode resultCode; if (uploadResult.isSuccess()) { - updateSyncedFileState(uploadResult, file.lastModified(), client); + final String latestEtag = getLatestEtag(uploadResult, client); + updateSyncedFileState(latestEtag, file.lastModified()); resultCode = uploadResult.getCode(); } else { resultCode = onUploadFailure(uploadResult.getCode(), file.getName()); } - DbHelper.manageSyncedFileStateDB(syncedState, "UPDATE", context); return new RemoteOperationResult(resultCode); } @@ -180,30 +180,49 @@ public class UploadFileOperation extends RemoteOperation { /** * Update syncedFileState (etag & last modified) in case of successful upload - * @param uploadResult The Upload's result instance + * @param latestEtag new eTag of the remoteFile * @param fileLastModified value of local file's last modified - * @param client The client used to check etag if missing */ - private void updateSyncedFileState(final RemoteOperationResult uploadResult, final long fileLastModified, final OwnCloudClient client) { - //The below if statement should only be called for chunked upload. But + private void updateSyncedFileState(@Nullable final String latestEtag, final long fileLastModified) { + if (latestEtag != null) { + syncedState.setLastEtag(latestEtag); + } + + syncedState.setLastModified(fileLastModified); + DbHelper.manageSyncedFileStateDB(syncedState, "UPDATE", context); + } + + private @Nullable String getLatestEtag(@NonNull final RemoteOperationResult uploadResult, @NonNull final OwnCloudClient client) { + + if (uploadResult.getResultData() != null) { + return uploadResult.getResultData(); + } + + //The below code should only be called for chunked upload. But //for some unknown reason, the simple file upload doesn't give the eTag in the result // so, I moved the code here as a security - if (uploadResult.getResultData() == null) { - final RemoteOperationResult result = readRemoteFile(syncedState.getRemotePath(), client); - final ArrayList resultData = result.getData(); - if (result.isSuccess() && resultData != null && !resultData.isEmpty()) { - final String latestETag = ((RemoteFile) resultData.get(0)).getEtag(); - uploadResult.setResultData(latestETag); - } + try { + return readLatestEtagFromCloud(client); + } catch (ClassCastException exception) { + Timber.w("Impossible to read eTag from cloud: %s", exception.getMessage()); + return null; } - final String etag = uploadResult.getResultData(); + } + + private @Nullable String readLatestEtagFromCloud(@NonNull final OwnCloudClient client) throws ClassCastException{ + final RemoteOperationResult result = readRemoteFile(syncedState.getRemotePath(), client); + if (!result.isSuccess()) return null; - if (etag != null) { - syncedState.setLastEtag(etag); + final ArrayList resultData = result.getData(); + + if (resultData == null || resultData.isEmpty()) { + return null; } - syncedState.setLastModified(fileLastModified); + + return ((RemoteFile) resultData.get(0)).getEtag(); } + /** * Perform an operation to check available space on server before to upload * @param client OwnCloudClient diff --git a/app/src/main/java/foundation/e/drive/utils/AccountUtils.java b/app/src/main/java/foundation/e/drive/utils/AccountUtils.java deleted file mode 100644 index d9b944d9b5dbfac22173abc143225701539df762..0000000000000000000000000000000000000000 --- a/app/src/main/java/foundation/e/drive/utils/AccountUtils.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright MURENA SAS 2022 - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package foundation.e.drive.utils; - -import static foundation.e.drive.utils.AppConstants.ACCOUNT_DATA_GROUPS; - -import android.accounts.Account; -import android.accounts.AccountManager; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import java.util.Arrays; -import java.util.Optional; - -public class AccountUtils { - - - @NonNull - public static Optional getPremiumGroup(@NonNull AccountManager accountManager, @Nullable Account account) { - if (account == null) { - return Optional.empty(); - } - - final String groupData = accountManager.getUserData(account, ACCOUNT_DATA_GROUPS); - - if (groupData == null || groupData.isEmpty()) { - return Optional.empty(); - } - - final String[] groups = groupData.split(","); - - return Arrays.stream(groups) - .filter(group -> group.contains("premium-")) - .findFirst(); - } -} 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 a9fd0c00e519b24d65f0f3e41dc179d78e0d7ea4..5a605ef421e355aaa95756fd564609983eb7b31d 100644 --- a/app/src/main/java/foundation/e/drive/utils/CommonUtils.java +++ b/app/src/main/java/foundation/e/drive/utils/CommonUtils.java @@ -1,6 +1,6 @@ /* * Copyright © CLEUS SAS 2018-2019. - * Copyright © ECORP SAS 2022-2023. + * Copyright © MURENA SAS 2022-2023. * All rights reserved. This program and the accompanying materials * are made available under the terms of the GNU Public License v3.0 * which accompanies this distribution, and is available at @@ -14,7 +14,6 @@ import android.accounts.AccountManager; import android.annotation.SuppressLint; import android.app.NotificationChannel; import android.app.NotificationManager; -import android.app.Service; import android.content.ClipData; import android.content.ClipboardManager; import android.content.ContentResolver; @@ -31,9 +30,6 @@ import java.text.StringCharacterIterator; import java.util.Locale; import foundation.e.drive.R; -import foundation.e.drive.account.AccountUserInfoWorker; -import foundation.e.drive.work.WorkRequestFactory; -import timber.log.Timber; import static foundation.e.drive.utils.AppConstants.MEDIA_SYNC_PROVIDER_AUTHORITY; import static foundation.e.drive.utils.AppConstants.METERED_NETWORK_ALLOWED_AUTHORITY; @@ -41,10 +37,6 @@ import static foundation.e.drive.utils.AppConstants.SETTINGS_SYNC_PROVIDER_AUTHO import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.work.ExistingPeriodicWorkPolicy; -import androidx.work.PeriodicWorkRequest; -import androidx.work.WorkManager; - /** * @author Vincent Bourgmayer @@ -54,41 +46,6 @@ import androidx.work.WorkManager; */ public abstract class CommonUtils { - /** - * Set ServiceUncaughtExceptionHandler to be the MainThread Exception Handler - * Or update the service which use it - * todo: check if the ServiceExceptionHandler could be remove - * @param service current service - */ - public static void setServiceUnCaughtExceptionHandler(@NonNull Service service) { - Thread.UncaughtExceptionHandler defaultUEH = Thread.getDefaultUncaughtExceptionHandler(); - if (defaultUEH != null && ServiceExceptionHandler.class.getSimpleName().equals(defaultUEH.getClass().getSimpleName())) { - ((ServiceExceptionHandler) defaultUEH).setService(service); - } else { - Thread.setDefaultUncaughtExceptionHandler(new ServiceExceptionHandler(service)); - } - } - - - /** - * This method retrieve Account corresponding to account's name and type - * - * @param accountName Account Name, shouldn't be null - * @param accountType account type - * @param am Account Manager - * @return Account or null if not found - */ - @Nullable - public static Account getAccount(@NonNull String accountName, @NonNull String accountType, @NonNull AccountManager am) { - Account[] accounts = am.getAccountsByType(accountType); - for (int i = -1, size = accounts.length; ++i < size; ) { - if (accounts[i].name.equals(accountName)) { - return accounts[i]; - } - } - return null; - } - /** * This method retrieve Account corresponding to account's type * @@ -148,11 +105,7 @@ public abstract class CommonUtils { public static boolean isMeteredNetworkAllowed(@NonNull Account account) { return ContentResolver.getSyncAutomatically(account, METERED_NETWORK_ALLOWED_AUTHORITY); } - - /* methods relative to file */ - - - + /** * Tell if there is internet connection * @@ -161,7 +114,6 @@ public abstract class CommonUtils { * @return True if there is connection, false either */ public static boolean haveNetworkConnection(@NonNull Context context, boolean meteredNetworkAllowed) { - Timber.v("haveNetworkConnection()"); final ConnectivityManager cm = context.getSystemService(ConnectivityManager.class); final NetworkCapabilities capabilities = cm.getNetworkCapabilities(cm.getActiveNetwork()); @@ -217,17 +169,6 @@ public abstract class CommonUtils { Toast.LENGTH_SHORT).show(); } - - /** - * Job for Widget & notification about quota - * - * @param workManager component used to register worker - */ - public static void registerPeriodicUserInfoChecking(@NonNull WorkManager workManager) { - final PeriodicWorkRequest workRequest = WorkRequestFactory.getPeriodicWorkRequest(WorkRequestFactory.WorkType.PERIODIC_USER_INFO); - workManager.enqueueUniquePeriodicWork(AccountUserInfoWorker.UNIQUE_WORK_NAME, ExistingPeriodicWorkPolicy.UPDATE, workRequest); - } - /** * Function for get build props through reflection * @@ -247,4 +188,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/widgets/EDriveWidget.java b/app/src/main/java/foundation/e/drive/widgets/EDriveWidget.java index 8c01a51e6755bfec3b59ca08f6ca467a603b829a..ddc61ff41e26b708a9393efc8634cf0b2450f63d 100644 --- a/app/src/main/java/foundation/e/drive/widgets/EDriveWidget.java +++ b/app/src/main/java/foundation/e/drive/widgets/EDriveWidget.java @@ -39,7 +39,7 @@ import java.util.Calendar; import java.util.Locale; import foundation.e.drive.R; -import foundation.e.drive.utils.AccountUtils; +import foundation.e.drive.account.AccountUtils; import foundation.e.drive.utils.AppConstants; import foundation.e.drive.utils.CommonUtils; import timber.log.Timber; @@ -256,10 +256,11 @@ public class EDriveWidget extends AppWidgetProvider { views.setTextViewText(R.id.planName, context.getString(R.string.free_plan, totalShownQuota)); - AccountUtils.getPremiumGroup(accountManager, account) - .ifPresent(group -> views.setTextViewText(R.id.planName, - context.getString(R.string.premium_plan, - group.split("-")[1]))); + final String premiumPlan = AccountUtils.getPremiumPlan(accountManager, account); + if (premiumPlan != null) { + views.setTextViewText(R.id.planName, + context.getString(R.string.premium_plan, premiumPlan)); + } views.setTextViewText(R.id.status, context.getString(R.string.progress_status, usedShownQuota, totalShownQuota)); diff --git a/app/src/main/java/foundation/e/drive/work/WorkerUtils.kt b/app/src/main/java/foundation/e/drive/work/WorkerUtils.kt new file mode 100644 index 0000000000000000000000000000000000000000..0575de175ce69ad8d05f17753c81ded1cb3731f7 --- /dev/null +++ b/app/src/main/java/foundation/e/drive/work/WorkerUtils.kt @@ -0,0 +1,70 @@ +/* + * Copyright © MURENA SAS 2023. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + */ +package foundation.e.drive.work + +import androidx.work.ExistingPeriodicWorkPolicy +import androidx.work.ExistingWorkPolicy +import androidx.work.WorkManager +import foundation.e.drive.account.AccountUserInfoWorker +import foundation.e.drive.periodicScan.FullScanWorker +import foundation.e.drive.periodicScan.PeriodicScanWorker +import foundation.e.drive.synchronization.SyncWorker +import foundation.e.drive.work.WorkRequestFactory.WorkType + +/** + * @author Vincent Bourgmayer + */ +object WorkerUtils { + + @JvmStatic + fun enqueuePeriodicUserInfoFetching(workManager: WorkManager) { + val request = WorkRequestFactory.getPeriodicWorkRequest(WorkType.PERIODIC_USER_INFO) + workManager.enqueueUniquePeriodicWork(AccountUserInfoWorker.UNIQUE_WORK_NAME, + ExistingPeriodicWorkPolicy.UPDATE, + request) + } + + @JvmStatic + fun enqueuePeriodicFullScan(workManager: WorkManager) { + val request = WorkRequestFactory.getPeriodicWorkRequest(WorkType.PERIODIC_SCAN) + workManager.enqueueUniquePeriodicWork(PeriodicScanWorker.UNIQUE_WORK_NAME, + ExistingPeriodicWorkPolicy.KEEP, + request) + } + + @JvmStatic + fun enqueueOneTimeFullScan(workManager: WorkManager, isForced: Boolean) { + if (isForced) { + val request = WorkRequestFactory.getOneTimeWorkRequest(WorkType.ONE_TIME_FORCED_FULL_SCAN, + null) + workManager.enqueueUniqueWork(FullScanWorker.UNIQUE_WORK_NAME, + ExistingWorkPolicy.REPLACE, + request) + return + } + + val request = WorkRequestFactory.getOneTimeWorkRequest(WorkType.ONE_TIME_FULL_SCAN, + null) + workManager.enqueueUniqueWork(FullScanWorker.UNIQUE_WORK_NAME, + ExistingWorkPolicy.KEEP, + request) + } + + @JvmStatic + fun enqueueOneTimeAppListGenerator(workManager: WorkManager) { + val request = WorkRequestFactory.getOneTimeWorkRequest(WorkType.ONE_TIME_APP_LIST, + null) + workManager.enqueue(request) + } + + @JvmStatic + fun enqueueOneTimeSync(workManager: WorkManager) { + val request = WorkRequestFactory.getOneTimeWorkRequest(WorkType.ONE_TIME_SYNC, null) + workManager.enqueueUniqueWork(SyncWorker.UNIQUE_WORK_NAME, ExistingWorkPolicy.KEEP, request) + } +} diff --git a/detekt.yml b/detekt.yml index 4efb86519050178026690426c89a3500c666d82e..fb4a8a38b4f8325fe2ee8f75206fe62fafd3e84f 100644 --- a/detekt.yml +++ b/detekt.yml @@ -14,6 +14,9 @@ style: ForbiddenComment: active: false + ReturnCount: + excludeGuardClauses: true + # Complexity rules complexity: diff --git a/dev_tools.sh b/dev_tools.sh new file mode 100755 index 0000000000000000000000000000000000000000..5f7d3e603783871bcd892b97fd7d4700b8a2fde7 --- /dev/null +++ b/dev_tools.sh @@ -0,0 +1,100 @@ + +# Author Vincent Bourgmayer +# 2023 November 22nd + +function printHelp() { + echo "Utilisation: ./eDrive_dev_tools.sh [opt] [(optionnal:) value] + Note: This script strongly rely on ADB, so it won't work if ADB is not installed + + Options list: + -a : add Account into the device + -c : clear eDrive's data + -C : clear adb logcat + -d : dump database (generate a database dumb on the device. The dump is reachable by the user) + -f : Trigger fullScan work (scan cloud & device then try to sync changed files) + -h : print this help + -L : (required value: true|false) : true: enable/ false: disable full log + -l : (option: --clear) : --clear: clear logcat before. Otherwise just display logcat. Default: false + -p : Pull database directory from device into current computer folder + -s : test sync : Create 10 empty files in 'Document' folder and try to upload them + "; +} + + +function registerAccount() { + echo "calling script to add account into device + + /"'!'"\\ Device need to be connecter to WI_FI + + /"'!'"\\ In order to let it work, + check that credentials & server URL + are set in registerAccount2.sh + + /i\\ please also consider, that the script will need to be updated if UI + changes + "; + + + ./registerAccount.sh +} + +function setFullLog() { + if [ -z "$1" ]; then + echo "The parameter -L require a value (true|false)" + elif [ "$1" != "true" ] && [ "$1" != "false" ]; then + echo "Invalid parameter: $1. You need to provide: (true|false)" + else + echo "Sending broacast with $1 as parameter" + adb shell am broadcast -a foundation.e.drive.action.FULL_LOG_ON_PROD --receiver-include-background --ez full_log_enable "$1" + fi +} + +function displayLogcat() { + adb logcat --pid=$(adb shell pidof -s foundation.e.drive) +} + +function clearLogcat() { + adb logcat --clear +} + +function pullDatabase() { + adb pull /data/data/foundation.e.drive/databases +} + +function clearAppData() { + echo "clear eDrive's data"; + adb shell pm clear foundation.e.drive +} + +function dumpDatabase() { + echo "Dumping database" + adb shell am broadcast -a foundation.e.drive.action.DUMP_DATABASE --receiver-include-background +} + +function forceFullScan() { + adb shell am broadcast -a foundation.e.drive.action.FORCE_SCAN --receiver-include-background +} + +function testSync() { + adb shell am broadcast -a foundation.e.drive.action.TEST_SYNC --receiver-include-background +} + +while getopts acCdfhL:lps flag +do + case "${flag}" in + a) registerAccount;; + c) clearAppData;; + C) clearLogcat;; + d) dumpDatabase;; + f) forceFullScan;; + h) printHelp;; + l) displayLogcat;; + L) setFullLog ${OPTARG};; + p) pullDatabase;; + s) testSync;; + esac +done + +echo "|----FINISHED----|" +exit 0 + diff --git a/registerAccount.sh b/registerAccount.sh new file mode 100755 index 0000000000000000000000000000000000000000..438228d150f6e3d55d61da9e78a2c7f11b3eecd1 --- /dev/null +++ b/registerAccount.sh @@ -0,0 +1,55 @@ + +# Open Add account settings page (doesn't work if already opened and not closed) +adb shell am start -a android.settings.ADD_ACCOUNT_SETTINGS + +sleep 1 +# Select /e/ account +# number of input depend on language set on the device + +adb shell input keyevent TAB +sleep 1 + +# Open the /e/ account : add account form +adb shell input keyevent ENTER + + +# CREDENTIALS & SERVER URL + +MAIL="PUT YOUR VALUE" +PWD="PUT YOUR VALUE" +SERVER_URI="PUT YOUR VALUE" + +sleep 1 +#Insert Login +adb shell input text $MAIL +adb shell input keyevent TAB + +sleep 1 +#Insert PWD +adb shell input text $PWD + +## IF NO server URI +if [ -z $SERVER_URI ] +then + adb shell input keyevent TAB + adb shell input keyevent ENTER +else + + # Move across element + adb shell input keyevent TAB + adb shell input keyevent TAB + adb shell input keyevent TAB + + # Enter server URI + adb shell input keyevent ENTER + adb shell input keyevent TAB + adb shell input text $SERVER_URI + + # Move again to login btn + adb shell input keyevent TAB + adb shell input keyevent TAB + adb shell input keyevent TAB + adb shell input keyevent TAB + adb shell input keyevent TAB + adb shell input keyevent ENTER +fi