Loading app/src/main/java/foundation/e/drive/EdriveApplication.java +5 −40 Original line number Diff line number Diff line /* * Copyright (C) 2025 MURENA SAS * Copyright (C) 2025 e Foundation * Copyright (C) MURENA SAS 2023-2024 * * 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 Loading @@ -22,16 +23,14 @@ import static timber.log.Timber.DebugTree; import android.accounts.Account; import android.accounts.AccountManager; import android.app.Application; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import androidx.annotation.NonNull; import foundation.e.drive.account.AccountUtils; import foundation.e.drive.database.FailedSyncPrefsManager; import foundation.e.drive.fileObservers.FileObserverManager; import foundation.e.drive.recovery.RecoveryManager; import foundation.e.drive.utils.AppConstants; import foundation.e.drive.utils.CommonUtils; import foundation.e.drive.utils.ReleaseTree; Loading Loading @@ -76,42 +75,8 @@ public class EdriveApplication extends Application { } private void setupEdriveRecovery() { final SharedPreferences prefs = getSharedPreferences(AppConstants.SHARED_PREFERENCE_NAME, Context.MODE_PRIVATE); boolean isDataAlreadyRecovered = prefs.getBoolean(AppConstants.IS_DATA_ALREADY_RECOVERED, false); if (!isDataAlreadyRecovered) { Timber.d("Data recovery is needed for eDrive to sync with cloud"); String accountName = AccountUtils.getAccount(getApplicationContext()).name; String accountType = getString(R.string.eelo_account_type); // Account removal Intent accountRemovedIntent = new Intent("foundation.e.drive.action.ACCOUNT_REMOVED"); accountRemovedIntent.setComponent(new ComponentName("foundation.e.drive", "foundation.e.drive.account.receivers.AccountRemoveCallbackReceiver")); accountRemovedIntent.putExtra(AccountManager.KEY_ACCOUNT_NAME, accountName); accountRemovedIntent.putExtra(AccountManager.KEY_ACCOUNT_TYPE, accountType); Timber.d("Sending foundation.e.drive.action.ACCOUNT_REMOVED for eDrive data recovery: " + "account name = %s, account type = %s", accountName, accountType); getApplicationContext().sendBroadcast(accountRemovedIntent); // Account addition Intent accountAddedIntent = new Intent("foundation.e.drive.action.ADD_ACCOUNT"); accountAddedIntent.setComponent(new ComponentName("foundation.e.drive", "foundation.e.drive.account.receivers.AccountAddedReceiver")); accountAddedIntent.putExtra(AccountManager.KEY_ACCOUNT_NAME, accountName); accountAddedIntent.putExtra(AccountManager.KEY_ACCOUNT_TYPE, accountType); Timber.d("Sending foundation.e.drive.action.ADD_ACCOUNT for eDrive data recovery: " + "account name = %s, account type = %s", accountName, accountType); getApplicationContext().sendBroadcast(accountAddedIntent); } else { Timber.d("Data recovery is not necessary for eDrive."); } RecoveryManager recoveryManager = new RecoveryManager(getApplicationContext()); recoveryManager.initiateRecovery(); } synchronized public void startRecursiveFileObserver() { Loading app/src/main/java/foundation/e/drive/account/receivers/AccountAddedReceiver.kt +12 −7 Original line number Diff line number Diff line /* * Copyright (C) 2025 MURENA SAS * Copyright (C) 2025 e Foundation * Copyright (C) MURENA SAS 2023-2024 * * 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 Loading @@ -23,6 +24,8 @@ import android.content.Intent import android.content.SharedPreferences import foundation.e.drive.R import foundation.e.drive.account.AccountUtils import foundation.e.drive.recovery.RecoveryManager import foundation.e.drive.recovery.RecoveryPreferences.RecoveryStatus.RecoveryCompleted import foundation.e.drive.utils.AppConstants import foundation.e.drive.utils.DavClientProvider import foundation.e.drive.work.WorkLauncher Loading Loading @@ -57,13 +60,15 @@ class AccountAddedReceiver() : BroadcastReceiver() { if (workLauncher.enqueueSetupWorkers(context)) { DavClientProvider.getInstance().cleanUp() workLauncher.enqueuePeriodicUserInfoFetching() handleDataRecovery(context) } } // Update SharedPref to not trigger future recovery Timber.d("Updating IS_DATA_ALREADY_RECOVERED = true in SharedPref to not trigger future recovery") prefs.edit() .putBoolean(AppConstants.IS_DATA_ALREADY_RECOVERED, true) .apply() private fun handleDataRecovery(context: Context) { val recoveryManager = RecoveryManager(context) if (recoveryManager.isRecoveryNeeded()) { recoveryManager.updateRecoveryStatus(RecoveryCompleted) } } /** Loading app/src/main/java/foundation/e/drive/account/receivers/AccountRemoveCallbackReceiver.java +2 −2 Original line number Diff line number Diff line /* * Copyright (C) 2025 MURENA SAS * Copyright (C) 2025 e Foundation * Copyright (C) MURENA SAS 2023-2024 * * 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 Loading Loading @@ -124,7 +125,6 @@ public class AccountRemoveCallbackReceiver extends BroadcastReceiver { .remove(SETUP_COMPLETED) .remove(INITIAL_FOLDER_NUMBER) .remove(AppConstants.KEY_LAST_SCAN_TIME) .remove(AppConstants.IS_DATA_ALREADY_RECOVERED) .apply(); } Loading app/src/main/java/foundation/e/drive/recovery/RecoveryManager.kt 0 → 100644 +97 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 e Foundation * * 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 <https://www.gnu.org/licenses/>. */ package foundation.e.drive.recovery import android.accounts.AccountManager import android.content.ComponentName import android.content.Context import android.content.Intent import foundation.e.drive.R import foundation.e.drive.account.AccountUtils.getAccount import foundation.e.drive.recovery.RecoveryPreferences.RecoveryStatus.RecoveryCompleted import foundation.e.drive.recovery.RecoveryPreferences.RecoveryStatus.RecoveryNeeded import timber.log.Timber class RecoveryManager( private val context: Context, ) { fun initiateRecovery() { if (isRecoveryNeeded()) { val (accountName, accountType) = getUserAccountInfo(context) if (accountName.isNotEmpty()) { Timber.d("Initiating data recovery for eDrive.") logoutUser(accountName, accountType) loginUser(accountName, accountType) } } } fun updateRecoveryStatus(status: RecoveryPreferences.RecoveryStatus) { RecoveryPreferences.updateRecoveryStatus(context, status) Timber.d("Updating recovery status to $status.") } fun isRecoveryNeeded(): Boolean = when (RecoveryPreferences.isRecoveryNeeded(context)) { RecoveryNeeded -> true RecoveryCompleted -> false } private fun getUserAccountInfo(context: Context): Pair<String, String> { val accountName = getAccount(context.applicationContext)?.name ?: "" val accountType: String = context.getString(R.string.eelo_account_type) return Pair(accountName, accountType) } private fun loginUser(accountName: String, accountType: String) { val accountAddedIntent = Intent("foundation.e.drive.action.ADD_ACCOUNT").apply { setComponent( ComponentName( "foundation.e.drive", "foundation.e.drive.account.receivers.AccountAddedReceiver" ) ) putExtra(AccountManager.KEY_ACCOUNT_NAME, accountName) putExtra(AccountManager.KEY_ACCOUNT_TYPE, accountType) } context.applicationContext.sendBroadcast(accountAddedIntent) Timber.d("Sending foundation.e.drive.action.ADD_ACCOUNT for eDrive data recovery.") Timber.d("Account name = $accountName, account type = $accountType") } private fun logoutUser(accountName: String, accountType: String) { val accountRemovedIntent = Intent("foundation.e.drive.action.ACCOUNT_REMOVED").apply { setComponent( ComponentName( "foundation.e.drive", "foundation.e.drive.account.receivers.AccountRemoveCallbackReceiver" ) ) putExtra(AccountManager.KEY_ACCOUNT_NAME, accountName) putExtra(AccountManager.KEY_ACCOUNT_TYPE, accountType) } context.applicationContext.sendBroadcast(accountRemovedIntent) Timber.d("Sending foundation.e.drive.action.ACCOUNT_REMOVED for eDrive data recovery.") Timber.d("Account name = $accountName, account type = $accountType") } } app/src/main/java/foundation/e/drive/recovery/RecoveryPreferences.kt 0 → 100644 +60 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 e Foundation * * 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 <https://www.gnu.org/licenses/>. */ package foundation.e.drive.recovery import android.content.Context import foundation.e.drive.recovery.RecoveryPreferences.RecoveryStatus.RecoveryCompleted import foundation.e.drive.recovery.RecoveryPreferences.RecoveryStatus.RecoveryNeeded object RecoveryPreferences { private const val PREFERENCES_NAME = "edrive_recovery" private const val KEY_IS_DATA_ALREADY_RECOVERED = "is_data_already_recovered" @JvmStatic fun isRecoveryNeeded(context: Context): RecoveryStatus { val preferences = context.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE) val isDataAlreadyRecovered = preferences.getBoolean(KEY_IS_DATA_ALREADY_RECOVERED, false) return if (isDataAlreadyRecovered) { RecoveryCompleted } else { RecoveryNeeded } } @JvmStatic fun updateRecoveryStatus(context: Context, status: RecoveryStatus) { val preferences = context.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE) when (status) { RecoveryCompleted -> { preferences.edit().putBoolean(KEY_IS_DATA_ALREADY_RECOVERED, true).apply() } RecoveryNeeded -> { preferences.edit().putBoolean(KEY_IS_DATA_ALREADY_RECOVERED, false).apply() } } } enum class RecoveryStatus { RecoveryNeeded, RecoveryCompleted } } Loading
app/src/main/java/foundation/e/drive/EdriveApplication.java +5 −40 Original line number Diff line number Diff line /* * Copyright (C) 2025 MURENA SAS * Copyright (C) 2025 e Foundation * Copyright (C) MURENA SAS 2023-2024 * * 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 Loading @@ -22,16 +23,14 @@ import static timber.log.Timber.DebugTree; import android.accounts.Account; import android.accounts.AccountManager; import android.app.Application; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import androidx.annotation.NonNull; import foundation.e.drive.account.AccountUtils; import foundation.e.drive.database.FailedSyncPrefsManager; import foundation.e.drive.fileObservers.FileObserverManager; import foundation.e.drive.recovery.RecoveryManager; import foundation.e.drive.utils.AppConstants; import foundation.e.drive.utils.CommonUtils; import foundation.e.drive.utils.ReleaseTree; Loading Loading @@ -76,42 +75,8 @@ public class EdriveApplication extends Application { } private void setupEdriveRecovery() { final SharedPreferences prefs = getSharedPreferences(AppConstants.SHARED_PREFERENCE_NAME, Context.MODE_PRIVATE); boolean isDataAlreadyRecovered = prefs.getBoolean(AppConstants.IS_DATA_ALREADY_RECOVERED, false); if (!isDataAlreadyRecovered) { Timber.d("Data recovery is needed for eDrive to sync with cloud"); String accountName = AccountUtils.getAccount(getApplicationContext()).name; String accountType = getString(R.string.eelo_account_type); // Account removal Intent accountRemovedIntent = new Intent("foundation.e.drive.action.ACCOUNT_REMOVED"); accountRemovedIntent.setComponent(new ComponentName("foundation.e.drive", "foundation.e.drive.account.receivers.AccountRemoveCallbackReceiver")); accountRemovedIntent.putExtra(AccountManager.KEY_ACCOUNT_NAME, accountName); accountRemovedIntent.putExtra(AccountManager.KEY_ACCOUNT_TYPE, accountType); Timber.d("Sending foundation.e.drive.action.ACCOUNT_REMOVED for eDrive data recovery: " + "account name = %s, account type = %s", accountName, accountType); getApplicationContext().sendBroadcast(accountRemovedIntent); // Account addition Intent accountAddedIntent = new Intent("foundation.e.drive.action.ADD_ACCOUNT"); accountAddedIntent.setComponent(new ComponentName("foundation.e.drive", "foundation.e.drive.account.receivers.AccountAddedReceiver")); accountAddedIntent.putExtra(AccountManager.KEY_ACCOUNT_NAME, accountName); accountAddedIntent.putExtra(AccountManager.KEY_ACCOUNT_TYPE, accountType); Timber.d("Sending foundation.e.drive.action.ADD_ACCOUNT for eDrive data recovery: " + "account name = %s, account type = %s", accountName, accountType); getApplicationContext().sendBroadcast(accountAddedIntent); } else { Timber.d("Data recovery is not necessary for eDrive."); } RecoveryManager recoveryManager = new RecoveryManager(getApplicationContext()); recoveryManager.initiateRecovery(); } synchronized public void startRecursiveFileObserver() { Loading
app/src/main/java/foundation/e/drive/account/receivers/AccountAddedReceiver.kt +12 −7 Original line number Diff line number Diff line /* * Copyright (C) 2025 MURENA SAS * Copyright (C) 2025 e Foundation * Copyright (C) MURENA SAS 2023-2024 * * 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 Loading @@ -23,6 +24,8 @@ import android.content.Intent import android.content.SharedPreferences import foundation.e.drive.R import foundation.e.drive.account.AccountUtils import foundation.e.drive.recovery.RecoveryManager import foundation.e.drive.recovery.RecoveryPreferences.RecoveryStatus.RecoveryCompleted import foundation.e.drive.utils.AppConstants import foundation.e.drive.utils.DavClientProvider import foundation.e.drive.work.WorkLauncher Loading Loading @@ -57,13 +60,15 @@ class AccountAddedReceiver() : BroadcastReceiver() { if (workLauncher.enqueueSetupWorkers(context)) { DavClientProvider.getInstance().cleanUp() workLauncher.enqueuePeriodicUserInfoFetching() handleDataRecovery(context) } } // Update SharedPref to not trigger future recovery Timber.d("Updating IS_DATA_ALREADY_RECOVERED = true in SharedPref to not trigger future recovery") prefs.edit() .putBoolean(AppConstants.IS_DATA_ALREADY_RECOVERED, true) .apply() private fun handleDataRecovery(context: Context) { val recoveryManager = RecoveryManager(context) if (recoveryManager.isRecoveryNeeded()) { recoveryManager.updateRecoveryStatus(RecoveryCompleted) } } /** Loading
app/src/main/java/foundation/e/drive/account/receivers/AccountRemoveCallbackReceiver.java +2 −2 Original line number Diff line number Diff line /* * Copyright (C) 2025 MURENA SAS * Copyright (C) 2025 e Foundation * Copyright (C) MURENA SAS 2023-2024 * * 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 Loading Loading @@ -124,7 +125,6 @@ public class AccountRemoveCallbackReceiver extends BroadcastReceiver { .remove(SETUP_COMPLETED) .remove(INITIAL_FOLDER_NUMBER) .remove(AppConstants.KEY_LAST_SCAN_TIME) .remove(AppConstants.IS_DATA_ALREADY_RECOVERED) .apply(); } Loading
app/src/main/java/foundation/e/drive/recovery/RecoveryManager.kt 0 → 100644 +97 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 e Foundation * * 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 <https://www.gnu.org/licenses/>. */ package foundation.e.drive.recovery import android.accounts.AccountManager import android.content.ComponentName import android.content.Context import android.content.Intent import foundation.e.drive.R import foundation.e.drive.account.AccountUtils.getAccount import foundation.e.drive.recovery.RecoveryPreferences.RecoveryStatus.RecoveryCompleted import foundation.e.drive.recovery.RecoveryPreferences.RecoveryStatus.RecoveryNeeded import timber.log.Timber class RecoveryManager( private val context: Context, ) { fun initiateRecovery() { if (isRecoveryNeeded()) { val (accountName, accountType) = getUserAccountInfo(context) if (accountName.isNotEmpty()) { Timber.d("Initiating data recovery for eDrive.") logoutUser(accountName, accountType) loginUser(accountName, accountType) } } } fun updateRecoveryStatus(status: RecoveryPreferences.RecoveryStatus) { RecoveryPreferences.updateRecoveryStatus(context, status) Timber.d("Updating recovery status to $status.") } fun isRecoveryNeeded(): Boolean = when (RecoveryPreferences.isRecoveryNeeded(context)) { RecoveryNeeded -> true RecoveryCompleted -> false } private fun getUserAccountInfo(context: Context): Pair<String, String> { val accountName = getAccount(context.applicationContext)?.name ?: "" val accountType: String = context.getString(R.string.eelo_account_type) return Pair(accountName, accountType) } private fun loginUser(accountName: String, accountType: String) { val accountAddedIntent = Intent("foundation.e.drive.action.ADD_ACCOUNT").apply { setComponent( ComponentName( "foundation.e.drive", "foundation.e.drive.account.receivers.AccountAddedReceiver" ) ) putExtra(AccountManager.KEY_ACCOUNT_NAME, accountName) putExtra(AccountManager.KEY_ACCOUNT_TYPE, accountType) } context.applicationContext.sendBroadcast(accountAddedIntent) Timber.d("Sending foundation.e.drive.action.ADD_ACCOUNT for eDrive data recovery.") Timber.d("Account name = $accountName, account type = $accountType") } private fun logoutUser(accountName: String, accountType: String) { val accountRemovedIntent = Intent("foundation.e.drive.action.ACCOUNT_REMOVED").apply { setComponent( ComponentName( "foundation.e.drive", "foundation.e.drive.account.receivers.AccountRemoveCallbackReceiver" ) ) putExtra(AccountManager.KEY_ACCOUNT_NAME, accountName) putExtra(AccountManager.KEY_ACCOUNT_TYPE, accountType) } context.applicationContext.sendBroadcast(accountRemovedIntent) Timber.d("Sending foundation.e.drive.action.ACCOUNT_REMOVED for eDrive data recovery.") Timber.d("Account name = $accountName, account type = $accountType") } }
app/src/main/java/foundation/e/drive/recovery/RecoveryPreferences.kt 0 → 100644 +60 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 e Foundation * * 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 <https://www.gnu.org/licenses/>. */ package foundation.e.drive.recovery import android.content.Context import foundation.e.drive.recovery.RecoveryPreferences.RecoveryStatus.RecoveryCompleted import foundation.e.drive.recovery.RecoveryPreferences.RecoveryStatus.RecoveryNeeded object RecoveryPreferences { private const val PREFERENCES_NAME = "edrive_recovery" private const val KEY_IS_DATA_ALREADY_RECOVERED = "is_data_already_recovered" @JvmStatic fun isRecoveryNeeded(context: Context): RecoveryStatus { val preferences = context.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE) val isDataAlreadyRecovered = preferences.getBoolean(KEY_IS_DATA_ALREADY_RECOVERED, false) return if (isDataAlreadyRecovered) { RecoveryCompleted } else { RecoveryNeeded } } @JvmStatic fun updateRecoveryStatus(context: Context, status: RecoveryStatus) { val preferences = context.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE) when (status) { RecoveryCompleted -> { preferences.edit().putBoolean(KEY_IS_DATA_ALREADY_RECOVERED, true).apply() } RecoveryNeeded -> { preferences.edit().putBoolean(KEY_IS_DATA_ALREADY_RECOVERED, false).apply() } } } enum class RecoveryStatus { RecoveryNeeded, RecoveryCompleted } }