Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Verified Commit 0e4153cc authored by Fahim M. Choudhury's avatar Fahim M. Choudhury
Browse files

feat: Implement device to cloud push for all the files needed for Murena.io cloud recovery

eDrive checks if a recovery is needed and logs out and logs in the user to delete and recreate its database. It uses two broadcasts to trigger account removed and account added receivers.

Because of the recreation of the database, all the files and (syncable) folders are pushed to cloud storage.

When the process completes, a separate SharedPreference is used to track if recovery is needed or not. It is done so because if user logs out and logs in from AccountManager, eDrive's initial preference is cleared. However, this recovery preference will not be cleared unless user explicitly clears eDrive's data.
parent 9a290f19
Loading
Loading
Loading
Loading
Loading
+4 −1
Original line number Diff line number Diff line
@@ -110,6 +110,9 @@
            <intent-filter>
                <action android:name="android.accounts.action.ACCOUNT_REMOVED"/>
            </intent-filter>
            <intent-filter>
                <action android:name="foundation.e.drive.action.ACCOUNT_REMOVED"/>
            </intent-filter>
        </receiver>

        <receiver android:name=".account.receivers.AccountAddedReceiver"
+23 −5
Original line number Diff line number Diff line
/*
 * 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
 * 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
 * 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;
@@ -20,6 +30,7 @@ import androidx.annotation.NonNull;

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;
@@ -59,6 +70,13 @@ public class EdriveApplication extends Application {
        }

        FailedSyncPrefsManager.getInstance(getApplicationContext()).clearPreferences();

        setupEdriveRecovery();
    }

    private void setupEdriveRecovery() {
        RecoveryManager recoveryManager = new RecoveryManager(getApplicationContext());
        recoveryManager.initiateRecovery();
    }

    synchronized public void startRecursiveFileObserver() {
+25 −5
Original line number Diff line number Diff line
/*
 * Copyright © MURENA SAS 2023-2024.
 * 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
 * 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
 * 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.account.receivers

@@ -14,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
@@ -48,6 +60,14 @@ class AccountAddedReceiver() : BroadcastReceiver() {
        if (workLauncher.enqueueSetupWorkers(context)) {
            DavClientProvider.getInstance().cleanUp()
            workLauncher.enqueuePeriodicUserInfoFetching()
            handleDataRecovery(context)
        }
}

    private fun handleDataRecovery(context: Context) {
        val recoveryManager = RecoveryManager(context)
        if (recoveryManager.isRecoveryNeeded()) {
            recoveryManager.updateRecoveryStatus(RecoveryCompleted)
        }
    }

+26 −6
Original line number Diff line number Diff line
/*
 * Copyright © ECORP 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
 * 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
 * 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.account.receivers;
@@ -25,6 +35,8 @@ import androidx.annotation.Nullable;
import androidx.work.WorkManager;

import java.io.File;
import java.util.ArrayList;
import java.util.List;

import foundation.e.drive.R;
import foundation.e.drive.database.DbHelper;
@@ -94,7 +106,15 @@ public class AccountRemoveCallbackReceiver extends BroadcastReceiver {
    }

    private boolean isInvalidAction(@NonNull Intent intent) {
        return !"android.accounts.action.ACCOUNT_REMOVED".equals(intent.getAction());
        List<String> actions = new ArrayList<>();
        actions.add("android.accounts.action.ACCOUNT_REMOVED");
        actions.add("foundation.e.drive.action.ACCOUNT_REMOVED");

        String intentAction = intent.getAction();

        boolean hasNecessaryIntentAction = actions.contains(intentAction);

        return !hasNecessaryIntentAction;
    }

    private void cleanSharedPreferences(@NonNull Context applicationContext, @NonNull SharedPreferences prefs) {
+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")
    }
}
Loading