diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index d0f189d338cb02ea4a850c92019280b1f76ef3db..e0236fb97e4a47ff3167aee996d47065edbcc6db 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -28,6 +28,9 @@ android:protectionLevel="signature" tools:ignore="ReservedSystemPermission" /> + + - - - - - + @@ -114,6 +109,14 @@ + + + + + diff --git a/app/src/main/java/foundation/e/drive/account/AccountSetupWorker.kt b/app/src/main/java/foundation/e/drive/account/AccountSetupWorker.kt new file mode 100644 index 0000000000000000000000000000000000000000..af4d331c5ec7160e0deb4a0150b8f1cb854fd6c0 --- /dev/null +++ b/app/src/main/java/foundation/e/drive/account/AccountSetupWorker.kt @@ -0,0 +1,110 @@ +/* + * 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.account + +import android.accounts.Account +import android.accounts.AccountManager +import android.content.Context +import android.content.SharedPreferences +import androidx.work.WorkManager +import androidx.work.Worker +import androidx.work.WorkerParameters +import foundation.e.drive.R +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.getSyncedFolderRoots +import timber.log.Timber + +const val accountNameKey = AccountManager.KEY_ACCOUNT_NAME +const val accountTypeKey = AccountManager.KEY_ACCOUNT_TYPE + +/** + * @author Vincent Bourgmayer + */ +class AccountSetupWorker(private val context: Context, private val workerParams: WorkerParameters) : + Worker(context, workerParams) { + + private var account: Account? = null + + override fun doWork(): Result { + val accountName = workerParams.inputData.getString(accountNameKey) + val accountType = workerParams.inputData.getString(accountTypeKey) + + val prefs = context.getSharedPreferences( + AppConstants.SHARED_PREFERENCE_NAME, + Context.MODE_PRIVATE) + + if (checkStartConditions(accountName, accountType, prefs)) { + + startSyncWorkers() + + prefs.edit() + .putString(AccountManager.KEY_ACCOUNT_NAME, accountName) + .apply() + + } + + DavClientProvider.getInstance().cleanUp() + return Result.success() + } + + + /** + * Check that conditions to start are met: + * - Setup has not already been done or not achieved + * - AccountName is not empty + * - AccountType is /e/ account + * - the account is effectively available through accountManager + */ + private fun checkStartConditions(accountName:String?, accountType:String?, prefs:SharedPreferences): Boolean { + val alreadyStoredAccountName = prefs.getString(AccountManager.KEY_ACCOUNT_NAME, "") + if (!alreadyStoredAccountName.isNullOrEmpty()) { //if no accountName found, next setup cannot have been done + val setupAlreadyDone = + prefs.getBoolean(AppConstants.INITIALIZATION_HAS_BEEN_DONE, false) + if (setupAlreadyDone) return false + + if (accountName != alreadyStoredAccountName) { + Timber.w("An account was already added but not fully setup") + //todo: check with Aude or Jonathan + // what to do if alreadyStoredAccountName & accountName are different. + // Which one to choose ? + } + } + + if (!isValidAccountType(accountType)) return false + if (accountName.isNullOrEmpty() || accountType.isNullOrEmpty()) return false + + account = CommonUtils.getAccount(accountName, accountType, AccountManager.get(context)) + if (account == null) { + Timber.w("got Invalid %s account for username: %s ", accountType, accountName) + return false + } + + return true + } + + private fun isValidAccountType(accountType: String?): Boolean { + val expectedAccountType = context.getString(R.string.eelo_account_type) + return accountType != null && accountType == expectedAccountType + } + + private fun startSyncWorkers() { + CommonUtils.registerPeriodicUserInfoChecking(WorkManager.getInstance(context)) + + val syncedFolders: List = getSyncedFolderRoots( + applicationContext + ) + CommonUtils.registerInitializationWorkers( + syncedFolders, WorkManager.getInstance( + applicationContext + ) + ) + } +} \ No newline at end of file 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 new file mode 100644 index 0000000000000000000000000000000000000000..75716fa4ef1010d9bcafa0010d586e14f53e4e74 --- /dev/null +++ b/app/src/main/java/foundation/e/drive/account/receivers/AccountAddedReceiver.kt @@ -0,0 +1,64 @@ +/* + * 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.account.receivers + +import android.accounts.AccountManager +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.os.Bundle +import androidx.work.Data +import androidx.work.OneTimeWorkRequestBuilder +import androidx.work.WorkManager +import foundation.e.drive.account.AccountSetupWorker +import foundation.e.drive.utils.AppConstants +import timber.log.Timber + +/** + * Entry point for eDrive + * @author Vincent Bourgmayer + */ +class AccountAddedReceiver() : BroadcastReceiver(){ + override fun onReceive(context: Context?, intent: Intent?) { + Timber.d("Received account added intent request") + + if (context == null || intent == null) return + + val data = prepareWorkerInputData(intent.extras) ?: return + + //register worker for setup + val workRequest = OneTimeWorkRequestBuilder() + .setInputData(data) + .addTag(AppConstants.WORK_GENERIC_TAG) + .build() + + WorkManager.getInstance(context).enqueue(workRequest) + /** + * as it is an experimentation right now it will be enough. But for final implementation we + * should answer the following design question + * 1. Shouldn't we rather use enqueueUniqueWork with replace/Ignore policy for older worker ? + * 2. Shouldn't we chain other worker required after account setup instead of triggering them from + * accountSetupWorker ? + */ + + + Timber.i("Account setup worker enqueued") + } + + private fun prepareWorkerInputData(extras: Bundle?): Data? { + val extras = extras ?: return null + + val accountName = extras.getString(AccountManager.KEY_ACCOUNT_NAME, "") + val accountType = extras.getString(AccountManager.KEY_ACCOUNT_TYPE, "") + + return Data.Builder() + .putString(AccountManager.KEY_ACCOUNT_NAME, accountName) + .putString(AccountManager.KEY_ACCOUNT_TYPE, accountType) + .build() + } +} \ No newline at end of file diff --git a/app/src/main/java/foundation/e/drive/receivers/AccountRemoveCallbackReceiver.java b/app/src/main/java/foundation/e/drive/receivers/AccountRemoveCallbackReceiver.java index fc7293f74ab57c99652d53801fd0c6cb5a68a999..09d5a6787b897ab5ea44ca879776532e90051c15 100644 --- a/app/src/main/java/foundation/e/drive/receivers/AccountRemoveCallbackReceiver.java +++ b/app/src/main/java/foundation/e/drive/receivers/AccountRemoveCallbackReceiver.java @@ -26,7 +26,6 @@ import java.io.File; import foundation.e.drive.EdriveApplication; import foundation.e.drive.database.DbHelper; import foundation.e.drive.database.FailedSyncPrefsManager; -import foundation.e.drive.services.InitializerService; import foundation.e.drive.services.ObserverService; import foundation.e.drive.services.SynchronizationService; import foundation.e.drive.utils.AppConstants; @@ -100,9 +99,9 @@ public class AccountRemoveCallbackReceiver extends BroadcastReceiver { boolean observerServiceStopResult = applicationContext.stopService(observerServiceIntent); Timber.d("stop ObserverService: %s", observerServiceStopResult); - Intent initializerServiceIntent = new Intent(applicationContext, InitializerService.class); - boolean initServiceStopResult = applicationContext.stopService(initializerServiceIntent); - Timber.d("stop InitializerService: %s", initServiceStopResult); + //Todo Intent initializerServiceIntent = new Intent(applicationContext, InitializerService.class); + //boolean initServiceStopResult = applicationContext.stopService(initializerServiceIntent); + //Timber.d("stop InitializerService: %s", initServiceStopResult); Intent synchronizationServiceIntent = new Intent(applicationContext, SynchronizationService.class); boolean syncServiceStopResult = applicationContext.stopService(synchronizationServiceIntent); 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 12593beed998e4aeb44a896bae643a08d63572b0..5616d55e3518b2c9e43ded1dd7eb093559e130ed 100644 --- a/app/src/main/java/foundation/e/drive/receivers/BootCompletedReceiver.java +++ b/app/src/main/java/foundation/e/drive/receivers/BootCompletedReceiver.java @@ -18,7 +18,6 @@ import androidx.annotation.NonNull; import foundation.e.drive.BuildConfig; import foundation.e.drive.database.DbHelper; -import foundation.e.drive.services.InitializerService; import foundation.e.drive.utils.AppConstants; import foundation.e.drive.utils.CommonUtils; import timber.log.Timber; @@ -72,7 +71,7 @@ public class BootCompletedReceiver extends BroadcastReceiver { .putBoolean(AppConstants.INITIALIZATION_HAS_BEEN_DONE, false) .apply(); - context.startService(new Intent(context, InitializerService.class)); + //Todo context.startService(new Intent(context, InitializerService.class)); forceDBUpdate(context); } diff --git a/app/src/main/java/foundation/e/drive/services/InitializerService.java b/app/src/main/java/foundation/e/drive/services/InitializerService.java deleted file mode 100644 index 7492739990b54bd593eceb2747fd8acb6b3b70ec..0000000000000000000000000000000000000000 --- a/app/src/main/java/foundation/e/drive/services/InitializerService.java +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Copyright © CLEUS SAS 2018-2019. - * Copyright © ECORP 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 - * http://www.gnu.org/licenses/gpl.html - */ - -package foundation.e.drive.services; - -import android.accounts.Account; -import android.accounts.AccountManager; -import android.app.Service; -import android.content.Context; -import android.content.Intent; -import android.content.SharedPreferences; -import android.os.IBinder; - -import java.io.File; -import java.util.List; - -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 timber.log.Timber; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.annotation.VisibleForTesting; -import androidx.work.WorkManager; - -/** - * @author Vincent Bourgmayer - * @author Jonathan Klee - * @author Abhishek Aggarwal - */ -public class InitializerService extends Service { - private Account account; - - @Override - public void onCreate() { - Timber.tag(InitializerService.class.getSimpleName()); - super.onCreate(); - } - - @Override - public int onStartCommand(@NonNull Intent intent, int flags, int startId) { - Timber.i("onStartCommand()"); - DavClientProvider.getInstance().cleanUp(); - CommonUtils.setServiceUnCaughtExceptionHandler(this); - - //Get account - final SharedPreferences prefs = this.getSharedPreferences(AppConstants.SHARED_PREFERENCE_NAME, Context.MODE_PRIVATE); - - String accountName = prefs.getString(AccountManager.KEY_ACCOUNT_NAME, ""); - String accountType = prefs.getString(AccountManager.KEY_ACCOUNT_TYPE, ""); - - if (accountName.isEmpty() && accountType.isEmpty() && intent.getExtras() != null) { - - accountName = intent.getExtras().getString(AccountManager.KEY_ACCOUNT_NAME, ""); - accountType = intent.getExtras().getString(AccountManager.KEY_ACCOUNT_TYPE, ""); - - prefs.edit().putString(AccountManager.KEY_ACCOUNT_NAME, accountName) - .putString(AccountManager.KEY_ACCOUNT_TYPE, accountType) - .apply(); - } - - if (checkStartConditions(prefs, accountName, accountType)) { - start(); - } - return super.onStartCommand(intent, flags, startId); - } - - /** - * Check if condition are present to start - * - Initialization not already done - * - AccountName is not empty - * - Account available - * @return true if condition are met - */ - @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) - public boolean checkStartConditions(@NonNull final SharedPreferences prefs, - @NonNull final String accountName, @NonNull final String accountType) { - if (prefs.getBoolean(AppConstants.INITIALIZATION_HAS_BEEN_DONE, false)) { - Timber.w("Initialization has already been done"); - return false; - } - - if (accountName.isEmpty()) { - Timber.w("No account Name available"); - return false; - } - - account = CommonUtils.getAccount(accountName, accountType, AccountManager.get(this)); - if (account == null) { - Timber.w("got Invalid %s account for username: %s ", accountType, accountName); - return false; - } - return true; - } - - /** - * Set up base component for eDrive: - * - Register basic worker - * - build root folders to sync - */ - private void start() { - Timber.d("start()"); - CommonUtils.registerPeriodicUserInfoChecking(WorkManager.getInstance(this)); - - final List syncedFolders = RootSyncedFolderProvider.INSTANCE.getSyncedFolderRoots(getApplicationContext()); - CommonUtils.registerInitializationWorkers(syncedFolders, WorkManager.getInstance(getApplicationContext()) ); - } - - @Nullable - @Override - public IBinder onBind(@Nullable Intent intent) { - return null; - } -} \ No newline at end of file diff --git a/app/src/main/java/foundation/e/drive/services/ObserverService.java b/app/src/main/java/foundation/e/drive/services/ObserverService.java index 8b1d6747c01413d0dd5d87d276e4be22f200eb89..de186c0b3d174a9d00cc4e8caef7a9b5f15def51 100644 --- a/app/src/main/java/foundation/e/drive/services/ObserverService.java +++ b/app/src/main/java/foundation/e/drive/services/ObserverService.java @@ -152,8 +152,8 @@ public class ObserverService extends Service implements OnRemoteOperationListene if (!prefs.getBoolean(INITIALIZATION_HAS_BEEN_DONE, false)) { Timber.d("Initialization hasn't been done"); - Intent initializerIntent = new Intent(this, InitializerService.class); - startService(initializerIntent); + //Todo Intent initializerIntent = new Intent(this, InitializerService.class); + //startService(initializerIntent); return false; } diff --git a/app/src/test/java/foundation/e/drive/services/InitializerServiceTest.java b/app/src/test/java/foundation/e/drive/services/InitializerServiceTest.java deleted file mode 100644 index ca5febde32ca4d45688701e970af2035ffb5371b..0000000000000000000000000000000000000000 --- a/app/src/test/java/foundation/e/drive/services/InitializerServiceTest.java +++ /dev/null @@ -1,65 +0,0 @@ -package foundation.e.drive.services; - -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; - -import android.accounts.AccountManager; -import android.app.job.JobScheduler; -import android.content.Context; -import android.net.ConnectivityManager; - -import androidx.test.core.app.ApplicationProvider; -import org.junit.Test; -import org.robolectric.Robolectric; - -import foundation.e.drive.TestUtils; -import foundation.e.drive.database.DbHelper; -import foundation.e.drive.utils.AppConstants; - -public class InitializerServiceTest extends AbstractServiceIT{ - - public InitializerServiceTest(){ - mServiceController = Robolectric.buildService(InitializerService.class); - mService = mServiceController.get(); - context = ApplicationProvider.getApplicationContext(); - accountManager = AccountManager.get(context); - jobScheduler = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE); - contentResolver = context.getContentResolver(); - sharedPreferences = context.getSharedPreferences( AppConstants.SHARED_PREFERENCE_NAME, Context.MODE_PRIVATE); - connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); - dbHelper = new DbHelper(context); - TestUtils.initializeWorkmanager(context); - init_done = false; - TestUtils.loadServerCredentials(); - TestUtils.prepareValidAccount(accountManager); - } - - @Test - public void checkStartConditions_noAccountInIntent_NorInPrefs_falseExpected() { - registerSharedPref(); - final boolean canStart = mService.checkStartConditions(sharedPreferences, "", ""); - assertFalse("InitializerService.checkStartConditions without any available account info should return false but returned true", canStart); - } - - @Test - public void checkStartConditions_trueExpected() { - registerSharedPref(); - final boolean canStart = mService.checkStartConditions(sharedPreferences, TestUtils.TEST_ACCOUNT_NAME, TestUtils.TEST_ACCOUNT_TYPE); - assertTrue("InitializerService.checkStartConditions with invalid account should return false but returned true", canStart); - } - - @Test - public void checkStartConditions_withFakeAccountNameOrType_falseExpected() { - registerSharedPref(); - final boolean canStart = mService.checkStartConditions(sharedPreferences, "any", "any"); - assertFalse("InitializerService.checkStartConditions with invalid account should return false but returned true", canStart); - } - - @Test - public void checkStartConditions_withInitDone_falseExpected() { - registerSharedPref(); - sharedPreferences.edit().putBoolean(AppConstants.INITIALIZATION_HAS_BEEN_DONE, true).apply(); - final boolean canStart = mService.checkStartConditions(sharedPreferences, TestUtils.TEST_ACCOUNT_NAME, TestUtils.TEST_ACCOUNT_TYPE); - assertFalse("InitializerService.checkStartConditions with valid account but init already done should return false but returned true", canStart); - } -} diff --git a/app/src/test/java/foundation/e/drive/services/ObserverServiceTest.java b/app/src/test/java/foundation/e/drive/services/ObserverServiceTest.java index bb9ff1ad2c6988ca46ed7dd90a86752dc151e2b1..2ee0142b218cfd735577979216082d9897bf34f3 100644 --- a/app/src/test/java/foundation/e/drive/services/ObserverServiceTest.java +++ b/app/src/test/java/foundation/e/drive/services/ObserverServiceTest.java @@ -267,10 +267,10 @@ public class ObserverServiceTest extends AbstractServiceIT { mServiceController.create().startCommand(0, 0); //How to assert this... ? - Intent expectedIntent = new Intent(mService, InitializerService.class); - Intent actualIntent = shadowOf(RuntimeEnvironment.application).getNextStartedService(); + //Todo Intent expectedIntent = new Intent(mService, InitializerService.class); + //Intent actualIntent = shadowOf(RuntimeEnvironment.application).getNextStartedService(); - assertEquals("Checked intent not the expected one", expectedIntent.getComponent(), actualIntent.getComponent()); + //assertEquals("Checked intent not the expected one", expectedIntent.getComponent(), actualIntent.getComponent()); List logs = ShadowLog.getLogs(); ShadowLog.LogItem lastLog = logs.get(logs.size()-1);