From b600623a4b8325311e43ec36abadc85d48ab7955 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Mon, 9 Nov 2026 18:04:18 +0100 Subject: [PATCH 1/6] Rename .java to .kt Signed-off-by: alperozturk96 --- .../{CheckRemoteWipeTask.java => CheckRemoteWipeTask.kt} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename app/src/main/java/com/owncloud/android/ui/asynctasks/{CheckRemoteWipeTask.java => CheckRemoteWipeTask.kt} (100%) 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.kt similarity index 100% rename from app/src/main/java/com/owncloud/android/ui/asynctasks/CheckRemoteWipeTask.java rename to app/src/main/java/com/owncloud/android/ui/asynctasks/CheckRemoteWipeTask.kt From 41bc441b0497a1e28f3c2d8f93baa13370560675 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Mon, 9 Nov 2026 18:04:19 +0100 Subject: [PATCH 2/6] convert to kotlin and make lifecycle aware Signed-off-by: alperozturk96 --- .../android/ui/activity/FileActivity.java | 3 +- .../ui/asynctasks/CheckRemoteWipeTask.kt | 85 +++++++++---------- 2 files changed, 42 insertions(+), 46 deletions(-) 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..52340cb5c3d6 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 @@ -103,7 +103,6 @@ import org.greenrobot.eventbus.Subscribe; import org.greenrobot.eventbus.ThreadMode; -import java.lang.ref.WeakReference; import java.util.ArrayList; import javax.inject.Inject; @@ -462,7 +461,7 @@ protected void requestCredentialsUpdate(Account account) { account = getAccount(); } - new CheckRemoteWipeTask(backgroundJobManager, account, new WeakReference<>(this)).execute(); + new CheckRemoteWipeTask(backgroundJobManager, account, this).execute(); } public void performCredentialsUpdate(Account account) { 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 index 0c659e75cf68..209b75383566 100644 --- a/app/src/main/java/com/owncloud/android/ui/asynctasks/CheckRemoteWipeTask.kt +++ b/app/src/main/java/com/owncloud/android/ui/asynctasks/CheckRemoteWipeTask.kt @@ -1,56 +1,53 @@ /* + * Nextcloud - Android Client * - * * 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 - * + * SPDX-FileCopyrightText: 2026 Alper Ozturk + * SPDX-License-Identifier: AGPL-3.0-or-later */ - -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; +package com.owncloud.android.ui.asynctasks + +import android.accounts.Account +import androidx.annotation.WorkerThread +import androidx.lifecycle.lifecycleScope +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 kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.NonCancellable +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext + +class CheckRemoteWipeTask( + private val backgroundJobManager: BackgroundJobManager, + private val account: Account, + private val fileActivity: FileActivity +) { + companion object { + private const val TAG = "CheckRemoteWipeTask" } - @Override - protected Boolean doInBackground(Void... voids) { - final FileActivity fileActivity = fileActivityWeakReference.get(); + fun execute(): Job = fileActivity.lifecycleScope.launch { + val isWipeRequested = withContext(Dispatchers.IO + NonCancellable) { + startAccountRemovalIfWipeRequested() + } - if (fileActivity == null) { - Log_OC.e(this, "Check for remote wipe: no context available"); - return Boolean.FALSE; + if (isWipeRequested || fileActivity.isFinishing || fileActivity.isDestroyed) { + return@launch } - 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)); + fileActivity.performCredentialsUpdate(account) + } + + @WorkerThread + private fun startAccountRemovalIfWipeRequested(): Boolean { + if (!CredentialsUpdateHelper(fileActivity).isRemoteWipeRequested(account)) { + Log_OC.e(TAG, "Check for remote wipe not needed -> update credentials") + return false } - return Boolean.TRUE; + backgroundJobManager.startAccountRemovalJob(account.name, true) + return true } } From c53073f8fdf27f6110ad9000ede18717dc3eead0 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Tue, 10 Nov 2026 07:43:32 +0100 Subject: [PATCH 3/6] use credentialsUpdateHelper in CheckRemoteWipeTask Signed-off-by: alperozturk96 --- .../ui/asynctasks/CheckRemoteWipeTask.kt | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) 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 index 209b75383566..3e3e7eedc5dc 100644 --- a/app/src/main/java/com/owncloud/android/ui/asynctasks/CheckRemoteWipeTask.kt +++ b/app/src/main/java/com/owncloud/android/ui/asynctasks/CheckRemoteWipeTask.kt @@ -7,9 +7,13 @@ package com.owncloud.android.ui.asynctasks import android.accounts.Account +import androidx.activity.compose.ManagedActivityResultLauncher import androidx.annotation.WorkerThread import androidx.lifecycle.lifecycleScope import com.nextcloud.client.jobs.BackgroundJobManager +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 @@ -28,6 +32,8 @@ class CheckRemoteWipeTask( private const val TAG = "CheckRemoteWipeTask" } + private val credentialsUpdateHelper = CredentialsUpdateHelper(fileActivity) + fun execute(): Job = fileActivity.lifecycleScope.launch { val isWipeRequested = withContext(Dispatchers.IO + NonCancellable) { startAccountRemovalIfWipeRequested() @@ -37,12 +43,24 @@ class CheckRemoteWipeTask( return@launch } - fileActivity.performCredentialsUpdate(account) + performCredentialsUpdate(account) + } + + private fun performCredentialsUpdate(account: Account) { + try { + credentialsUpdateHelper.invalidateCredentials(account) + fileActivity.startActivityForResult( + credentialsUpdateHelper.createUpdateCredentialsIntent(account), + FileActivity.REQUEST_CODE__UPDATE_CREDENTIALS + ) + } catch (_: AccountUtils.AccountNotFoundException) { + SnackbarUtil.show(fileActivity, R.string.auth_account_does_not_exist) + } } @WorkerThread private fun startAccountRemovalIfWipeRequested(): Boolean { - if (!CredentialsUpdateHelper(fileActivity).isRemoteWipeRequested(account)) { + if (!credentialsUpdateHelper.isRemoteWipeRequested(account)) { Log_OC.e(TAG, "Check for remote wipe not needed -> update credentials") return false } From 5a1e0172ef9b4a2c40c53927626613e6e6e46ffe Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Tue, 10 Nov 2026 07:48:48 +0100 Subject: [PATCH 4/6] use activity result contracts Signed-off-by: alperozturk96 --- .../android/ui/activity/FileActivity.java | 19 ++------- .../ui/asynctasks/CheckRemoteWipeTask.kt | 40 +++++++++---------- 2 files changed, 24 insertions(+), 35 deletions(-) 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 52340cb5c3d6..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; @@ -141,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"; @@ -185,6 +183,8 @@ public abstract class FileActivity extends DrawerActivity @Inject ArbitraryDataProvider arbitraryDataProvider; + private final CheckRemoteWipeTask checkRemoteWipeTask = new CheckRemoteWipeTask(this); + private FilesRepository filesRepository; @Override @@ -461,18 +461,7 @@ protected void requestCredentialsUpdate(Account account) { account = getAccount(); } - new CheckRemoteWipeTask(backgroundJobManager, account, 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.kt b/app/src/main/java/com/owncloud/android/ui/asynctasks/CheckRemoteWipeTask.kt index 3e3e7eedc5dc..bd5e73eb3226 100644 --- a/app/src/main/java/com/owncloud/android/ui/asynctasks/CheckRemoteWipeTask.kt +++ b/app/src/main/java/com/owncloud/android/ui/asynctasks/CheckRemoteWipeTask.kt @@ -7,7 +7,7 @@ package com.owncloud.android.ui.asynctasks import android.accounts.Account -import androidx.activity.compose.ManagedActivityResultLauncher +import androidx.activity.result.contract.ActivityResultContracts import androidx.annotation.WorkerThread import androidx.lifecycle.lifecycleScope import com.nextcloud.client.jobs.BackgroundJobManager @@ -23,43 +23,43 @@ import kotlinx.coroutines.NonCancellable import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -class CheckRemoteWipeTask( - private val backgroundJobManager: BackgroundJobManager, - private val account: Account, - private val fileActivity: FileActivity -) { +class CheckRemoteWipeTask(private val fileActivity: FileActivity) { companion object { private const val TAG = "CheckRemoteWipeTask" } private val credentialsUpdateHelper = CredentialsUpdateHelper(fileActivity) - fun execute(): Job = fileActivity.lifecycleScope.launch { - val isWipeRequested = withContext(Dispatchers.IO + NonCancellable) { - startAccountRemovalIfWipeRequested() - } + private val credentialsUpdateLauncher = + fileActivity.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { } - if (isWipeRequested || fileActivity.isFinishing || fileActivity.isDestroyed) { - return@launch - } + fun execute(account: Account, backgroundJobManager: BackgroundJobManager): Job = + fileActivity.lifecycleScope.launch { + val isWipeRequested = withContext(Dispatchers.IO + NonCancellable) { + startAccountRemovalIfWipeRequested(account, backgroundJobManager) + } - performCredentialsUpdate(account) - } + if (isWipeRequested || fileActivity.isFinishing || fileActivity.isDestroyed) { + return@launch + } + + performCredentialsUpdate(account) + } private fun performCredentialsUpdate(account: Account) { try { credentialsUpdateHelper.invalidateCredentials(account) - fileActivity.startActivityForResult( - credentialsUpdateHelper.createUpdateCredentialsIntent(account), - FileActivity.REQUEST_CODE__UPDATE_CREDENTIALS - ) + credentialsUpdateLauncher.launch(credentialsUpdateHelper.createUpdateCredentialsIntent(account)) } catch (_: AccountUtils.AccountNotFoundException) { SnackbarUtil.show(fileActivity, R.string.auth_account_does_not_exist) } } @WorkerThread - private fun startAccountRemovalIfWipeRequested(): Boolean { + 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 From 488d8fba5c84bf58374a1a21b03e2e618f9ea25f Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Tue, 10 Nov 2026 07:48:55 +0100 Subject: [PATCH 5/6] update references Signed-off-by: alperozturk96 --- .../assets/worked-example.md | 3 +-- .../references/HOST-CAPABILITIES.md | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) 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 | From 8f097dd376354da3ec29eb49babd9d2bef3cc0a3 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Tue, 10 Nov 2026 08:01:00 +0100 Subject: [PATCH 6/6] use extension Signed-off-by: alperozturk96 --- .../com/owncloud/android/ui/asynctasks/CheckRemoteWipeTask.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 index bd5e73eb3226..78df69d773f8 100644 --- a/app/src/main/java/com/owncloud/android/ui/asynctasks/CheckRemoteWipeTask.kt +++ b/app/src/main/java/com/owncloud/android/ui/asynctasks/CheckRemoteWipeTask.kt @@ -11,6 +11,7 @@ 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 @@ -39,7 +40,7 @@ class CheckRemoteWipeTask(private val fileActivity: FileActivity) { startAccountRemovalIfWipeRequested(account, backgroundJobManager) } - if (isWipeRequested || fileActivity.isFinishing || fileActivity.isDestroyed) { + if (isWipeRequested || !fileActivity.isActive()) { return@launch }