diff --git a/.claude/skills/android-activity-to-navigator-fragment/assets/worked-example.md b/.claude/skills/android-activity-to-navigator-fragment/assets/worked-example.md index 9568ec0dc97b..c8e575180e8f 100644 --- a/.claude/skills/android-activity-to-navigator-fragment/assets/worked-example.md +++ b/.claude/skills/android-activity-to-navigator-fragment/assets/worked-example.md @@ -29,8 +29,7 @@ This conversion was the hardest one so far, because the Activity extended `FileA - Host stays a `DrawerActivity`. The credential flow moves into the fragment. - To avoid copying `FileActivity` logic, `ui/helpers/CredentialsUpdateHelper.kt` was - extracted. `FileActivity.performCredentialsUpdate`, `CheckRemoteWipeTask` and the fragment - all use it. + extracted. `CheckRemoteWipeTask` (used by `FileActivity`) and the fragment both use it. ## Resulting files diff --git a/.claude/skills/android-activity-to-navigator-fragment/references/HOST-CAPABILITIES.md b/.claude/skills/android-activity-to-navigator-fragment/references/HOST-CAPABILITIES.md index c236a260f7c0..3df61c8fd7f8 100644 --- a/.claude/skills/android-activity-to-navigator-fragment/references/HOST-CAPABILITIES.md +++ b/.claude/skills/android-activity-to-navigator-fragment/references/HOST-CAPABILITIES.md @@ -28,8 +28,8 @@ navigator screen a `FileActivity` would bind `OperationsService` for all of them |---|---| | `fileOperationsHelper.*` (queues operations on `OperationsService`) | Run the `RemoteOperation`/`SyncOperation` directly in `lifecycleScope` on `Dispatchers.IO` with a client for the right user | | `onRemoteOperationFinish(op, result)` override | Handle the result inline after the coroutine returns | -| `requestCredentialsUpdate(account)` → `CheckRemoteWipeTask` → `performCredentialsUpdate` | `CredentialsUpdateHelper` (`isRemoteWipeRequested`, `invalidateCredentials`, `createUpdateCredentialsIntent`) + `registerForActivityResult` | -| `onActivityResult(REQUEST_CODE__UPDATE_CREDENTIALS, RESULT_OK)` | the `ActivityResultLauncher` callback | +| `requestCredentialsUpdate(account)` → `CheckRemoteWipeTask.execute` (launches `AuthenticatorActivity` through its own `ActivityResultLauncher`) | `CredentialsUpdateHelper` (`isRemoteWipeRequested`, `invalidateCredentials`, `createUpdateCredentialsIntent`) + `registerForActivityResult` | +| Reacting to the credentials update result | the `ActivityResultLauncher` callback | | `showLoadingDialog` / `dismissLoadingDialog` | `LoadingDialog.newInstance(msg).show(childFragmentManager, TAG)` and `dismissAllowingStateLoss()` | | `EXTRA_USER` → `setUser` in `onCreate` | fragment reads `activity?.intent` and calls `navigatorActivity?.setUser` | | `EXTRA_FILE` → `file` | read it in the fragment if it is used, otherwise drop it from the callers | diff --git a/app/src/main/java/com/owncloud/android/ui/activity/FileActivity.java b/app/src/main/java/com/owncloud/android/ui/activity/FileActivity.java index ac256cd2375c..077ce965b11c 100644 --- a/app/src/main/java/com/owncloud/android/ui/activity/FileActivity.java +++ b/app/src/main/java/com/owncloud/android/ui/activity/FileActivity.java @@ -91,7 +91,6 @@ import com.owncloud.android.ui.fragment.albums.AlbumsFragment; import com.owncloud.android.ui.fragment.filesRepository.FilesRepository; import com.owncloud.android.ui.fragment.filesRepository.RemoteFilesRepository; -import com.owncloud.android.ui.helpers.CredentialsUpdateHelper; import com.owncloud.android.ui.helpers.FileOperationsHelper; import com.owncloud.android.ui.navigation.model.ActionBarStyle; import com.owncloud.android.ui.preview.media.PreviewMediaActivity; @@ -103,7 +102,6 @@ import org.greenrobot.eventbus.Subscribe; import org.greenrobot.eventbus.ThreadMode; -import java.lang.ref.WeakReference; import java.util.ArrayList; import javax.inject.Inject; @@ -142,8 +140,7 @@ public abstract class FileActivity extends DrawerActivity private static final String KEY_WAITING_FOR_OP_ID = "WAITING_FOR_OP_ID"; private static final String KEY_ACTION_BAR_TITLE = "ACTION_BAR_TITLE"; - public static final int REQUEST_CODE__UPDATE_CREDENTIALS = 0; - public static final int REQUEST_CODE__LAST_SHARED = REQUEST_CODE__UPDATE_CREDENTIALS; + public static final int REQUEST_CODE__LAST_SHARED = 0; /* Dialog tags */ private static final String DIALOG_UNTRUSTED_CERT = "DIALOG_UNTRUSTED_CERT"; @@ -186,6 +183,8 @@ public abstract class FileActivity extends DrawerActivity @Inject ArbitraryDataProvider arbitraryDataProvider; + private final CheckRemoteWipeTask checkRemoteWipeTask = new CheckRemoteWipeTask(this); + private FilesRepository filesRepository; @Override @@ -462,18 +461,7 @@ protected void requestCredentialsUpdate(Account account) { account = getAccount(); } - new CheckRemoteWipeTask(backgroundJobManager, account, new WeakReference<>(this)).execute(); - } - - public void performCredentialsUpdate(Account account) { - final var credentialsUpdateHelper = new CredentialsUpdateHelper(this); - try { - credentialsUpdateHelper.invalidateCredentials(account); - startActivityForResult(credentialsUpdateHelper.createUpdateCredentialsIntent(account), - REQUEST_CODE__UPDATE_CREDENTIALS); - } catch (com.owncloud.android.lib.common.accounts.AccountUtils.AccountNotFoundException e) { - SnackbarUtil.show(this, R.string.auth_account_does_not_exist); - } + checkRemoteWipeTask.execute(account, backgroundJobManager); } /** diff --git a/app/src/main/java/com/owncloud/android/ui/asynctasks/CheckRemoteWipeTask.java b/app/src/main/java/com/owncloud/android/ui/asynctasks/CheckRemoteWipeTask.java deleted file mode 100644 index 0c659e75cf68..000000000000 --- a/app/src/main/java/com/owncloud/android/ui/asynctasks/CheckRemoteWipeTask.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * - * * Nextcloud Android client application - * * - * * @author Tobias Kaminsky - * * Copyright (C) 2019 Tobias Kaminsky - * * Copyright (C) 2019 Nextcloud GmbH - * * - * SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only - * - */ - -package com.owncloud.android.ui.asynctasks; - -import android.accounts.Account; -import android.os.AsyncTask; - -import com.nextcloud.client.jobs.BackgroundJobManager; -import com.owncloud.android.lib.common.utils.Log_OC; -import com.owncloud.android.ui.activity.FileActivity; -import com.owncloud.android.ui.helpers.CredentialsUpdateHelper; - -import java.lang.ref.WeakReference; - -public class CheckRemoteWipeTask extends AsyncTask { - private BackgroundJobManager backgroundJobManager; - private Account account; - private WeakReference fileActivityWeakReference; - - public CheckRemoteWipeTask(BackgroundJobManager backgroundJobManager, - Account account, - WeakReference fileActivityWeakReference) { - this.backgroundJobManager = backgroundJobManager; - this.account = account; - this.fileActivityWeakReference = fileActivityWeakReference; - } - - @Override - protected Boolean doInBackground(Void... voids) { - final FileActivity fileActivity = fileActivityWeakReference.get(); - - if (fileActivity == null) { - Log_OC.e(this, "Check for remote wipe: no context available"); - return Boolean.FALSE; - } - - if (new CredentialsUpdateHelper(fileActivity).isRemoteWipeRequested(account)) { - backgroundJobManager.startAccountRemovalJob(account.name, true); - } else { - Log_OC.e(this, "Check for remote wipe not needed -> update credentials"); - fileActivity.runOnUiThread(() -> fileActivity.performCredentialsUpdate(account)); - } - - return Boolean.TRUE; - } -} diff --git a/app/src/main/java/com/owncloud/android/ui/asynctasks/CheckRemoteWipeTask.kt b/app/src/main/java/com/owncloud/android/ui/asynctasks/CheckRemoteWipeTask.kt new file mode 100644 index 000000000000..78df69d773f8 --- /dev/null +++ b/app/src/main/java/com/owncloud/android/ui/asynctasks/CheckRemoteWipeTask.kt @@ -0,0 +1,72 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2026 Alper Ozturk + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +package com.owncloud.android.ui.asynctasks + +import android.accounts.Account +import androidx.activity.result.contract.ActivityResultContracts +import androidx.annotation.WorkerThread +import androidx.lifecycle.lifecycleScope +import com.nextcloud.client.jobs.BackgroundJobManager +import com.nextcloud.utils.extensions.isActive +import com.nextcloud.utils.SnackbarUtil +import com.owncloud.android.R +import com.owncloud.android.lib.common.accounts.AccountUtils +import com.owncloud.android.lib.common.utils.Log_OC +import com.owncloud.android.ui.activity.FileActivity +import com.owncloud.android.ui.helpers.CredentialsUpdateHelper +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.NonCancellable +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext + +class CheckRemoteWipeTask(private val fileActivity: FileActivity) { + companion object { + private const val TAG = "CheckRemoteWipeTask" + } + + private val credentialsUpdateHelper = CredentialsUpdateHelper(fileActivity) + + private val credentialsUpdateLauncher = + fileActivity.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { } + + fun execute(account: Account, backgroundJobManager: BackgroundJobManager): Job = + fileActivity.lifecycleScope.launch { + val isWipeRequested = withContext(Dispatchers.IO + NonCancellable) { + startAccountRemovalIfWipeRequested(account, backgroundJobManager) + } + + if (isWipeRequested || !fileActivity.isActive()) { + return@launch + } + + performCredentialsUpdate(account) + } + + private fun performCredentialsUpdate(account: Account) { + try { + credentialsUpdateHelper.invalidateCredentials(account) + credentialsUpdateLauncher.launch(credentialsUpdateHelper.createUpdateCredentialsIntent(account)) + } catch (_: AccountUtils.AccountNotFoundException) { + SnackbarUtil.show(fileActivity, R.string.auth_account_does_not_exist) + } + } + + @WorkerThread + private fun startAccountRemovalIfWipeRequested( + account: Account, + backgroundJobManager: BackgroundJobManager + ): Boolean { + if (!credentialsUpdateHelper.isRemoteWipeRequested(account)) { + Log_OC.e(TAG, "Check for remote wipe not needed -> update credentials") + return false + } + + backgroundJobManager.startAccountRemovalJob(account.name, true) + return true + } +}