Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2026 Alper Ozturk <alper.ozturk@nextcloud.com>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

package com.owncloud.android.operations.e2e

import android.content.Context
import android.os.Handler
import android.os.Looper
import android.util.Log
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.nextcloud.client.account.User
import com.nextcloud.client.network.ClientFactory
import com.owncloud.android.R
import com.owncloud.android.lib.common.utils.Log_OC
import com.owncloud.android.lib.resources.e2ee.DeleteEncryptedFilesRemoteOperation
import com.owncloud.android.lib.resources.users.DeletePrivateKeyRemoteOperation
import com.owncloud.android.lib.resources.users.DeletePublicKeyRemoteOperation

@Suppress("MagicNumber")
class E2EDeletionService(private val clientFactory: ClientFactory) {
private val mainHandler = Handler(Looper.getMainLooper())

fun showRemoveE2EKeysAndFilesAlertDialog(context: Context, user: User, onResult: (Boolean) -> Unit) {
MaterialAlertDialogBuilder(context, R.style.FallbackTheming_Dialog)
.setTitle(R.string.prefs_remove_e2e_keys_and_files)
.setMessage(R.string.remove_e2e_keys_and_files_dialog_warning)
.setCancelable(true)
.setNegativeButton(R.string.common_cancel) { dialog, _ -> dialog.dismiss() }
.setPositiveButton(R.string.common_ok) { dialog, _ ->
deleteKeysAndFiles(user) {
dialog.dismiss()
onResult(it)
}
}
.show()
}

private fun deleteKeysAndFiles(user: User, onResult: (Boolean) -> Unit) {
Thread {
val result = runCatching {
val client = clientFactory.createNextcloudClient(user)
var successfulOperationResultCount = 3

if (!DeletePrivateKeyRemoteOperation().execute(client).isSuccess) {
successfulOperationResultCount -= 1
}

Log_OC.i(TAG, "🔑" + "private key is deleted")

if (!DeletePublicKeyRemoteOperation().execute(client).isSuccess) {
successfulOperationResultCount -= 1
}

Log_OC.i(TAG, "🗝" + "public key is deleted")

if (!DeleteEncryptedFilesRemoteOperation().execute(client).isSuccess) {
successfulOperationResultCount -= 1
}

Log_OC.i(TAG, "🗂️" + "encrypted files are deleted")

successfulOperationResultCount == 3
}.getOrElse { e ->
Log.e(TAG, "Cannot delete E2E keys and files", e)
false
}

mainHandler.post { onResult(result) }
}.start()
}

companion object {
private val TAG = E2EDeletionService::class.java.simpleName
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
import com.nextcloud.client.preferences.DarkMode;
import com.nextcloud.utils.extensions.ContextExtensionsKt;
import com.nextcloud.utils.mdm.MDMConfig;
import com.owncloud.android.BuildConfig;
import com.owncloud.android.MainApp;
import com.owncloud.android.R;
import com.owncloud.android.authentication.AuthenticatorActivity;
Expand All @@ -63,6 +64,7 @@
import com.owncloud.android.lib.common.ExternalLink;
import com.owncloud.android.lib.common.ExternalLinkType;
import com.owncloud.android.lib.common.utils.Log_OC;
import com.owncloud.android.operations.e2e.E2EDeletionService;
import com.owncloud.android.providers.DocumentsStorageProvider;
import com.owncloud.android.ui.ThemeableSwitchPreference;
import com.owncloud.android.ui.asynctasks.LoadingVersionNumberTask;
Expand All @@ -78,7 +80,6 @@
import com.owncloud.android.utils.theme.CapabilityUtils;
import com.owncloud.android.utils.theme.ViewThemeUtils;

import java.util.List;
import java.util.Objects;

import javax.inject.Inject;
Expand All @@ -91,7 +92,6 @@
import androidx.core.content.ContextCompat;
import androidx.core.content.res.ResourcesCompat;
import kotlin.Unit;
import kotlin.jvm.functions.Function1;

import static com.owncloud.android.ui.activity.DrawerActivity.REQ_ALL_FILES_ACCESS;

Expand Down Expand Up @@ -138,6 +138,8 @@ public class SettingsActivity extends PreferenceActivity
private String storagePath;
private String pendingLock;

private E2EDeletionService e2EDeletionService;

private User user;
@Inject ArbitraryDataProvider arbitraryDataProvider;
@Inject AppPreferences preferences;
Expand All @@ -164,6 +166,7 @@ public void onCreate(Bundle savedInstanceState) {
PreferenceScreen preferenceScreen = (PreferenceScreen) findPreference("preference_screen");

user = accountManager.getUser();
e2EDeletionService = new E2EDeletionService(clientFactory);

// retrieve user's base uri
setupBaseUri();
Expand Down Expand Up @@ -368,6 +371,8 @@ private void setupMoreCategory() {

removeE2E(preferenceCategoryMore);

removeE2EFilesAndKeys(preferenceCategoryMore);

setupHelpPreference(preferenceCategoryMore);

setupRecommendPreference(preferenceCategoryMore);
Expand Down Expand Up @@ -537,6 +542,46 @@ private void removeE2E(PreferenceCategory preferenceCategoryMore) {
}
}

private void removeE2EFilesAndKeys(PreferenceCategory preferenceCategoryMore) {
if (BuildConfig.DEBUG) {
Preference removeKeysAndFilesPreference = findPreference("remove_e2e_files_and_keys");
if (removeKeysAndFilesPreference != null) {
if (!FileOperationsHelper.isEndToEndEncryptionSetup(this, user)) {
preferenceCategoryMore.removePreference(removeKeysAndFilesPreference);
} else {
removeKeysAndFilesPreference.setOnPreferenceClickListener(p -> {
showRemoveE2EKeysAndFilesAlertDialog(preferenceCategoryMore, removeKeysAndFilesPreference);
return true;
});
}
}
}
}

private void showRemoveE2EKeysAndFilesAlertDialog(PreferenceCategory preferenceCategoryMore, Preference preference) {
if (e2EDeletionService == null) {
return;
}

e2EDeletionService.showRemoveE2EKeysAndFilesAlertDialog(this, user, success -> {
if (success) {
EncryptionUtils.removeE2E(arbitraryDataProvider, user);
preferenceCategoryMore.removePreference(preference);

Preference pMnemonic = findPreference("mnemonic");
if (pMnemonic != null) {
preferenceCategoryMore.removePreference(pMnemonic);
}

Preference pRemoveE2E = findPreference("remove_e2e");
if (pRemoveE2E != null) {
preferenceCategoryMore.removePreference(pRemoveE2E);
}
}
return Unit.INSTANCE;
});
}

private void showRemoveE2EAlertDialog(PreferenceCategory preferenceCategoryMore, Preference preference) {
new MaterialAlertDialogBuilder(this, R.style.FallbackTheming_Dialog)
.setTitle(R.string.prefs_e2e_mnemonic)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,11 @@ class FileListLayoutManager(private val fragment: OCFileListFragment, private va
* @param folder The folder to check, or `null` to refer to the root folder.
* @return `true` if the folder should be displayed in grid mode, `false` if list mode is preferred.
*/
fun isGridViewPreferred(folder: OCFile?): Boolean {
return if (fragment.searchEvent != null) {
(fragment.searchEvent.toSearchType() != SearchType.SHARED_FILTER) &&
OCFileListFragment.FOLDER_LAYOUT_GRID == preferences.getFolderLayout(folder)
} else {
fun isGridViewPreferred(folder: OCFile?): Boolean = if (fragment.searchEvent != null) {
(fragment.searchEvent.toSearchType() != SearchType.SHARED_FILTER) &&
OCFileListFragment.FOLDER_LAYOUT_GRID == preferences.getFolderLayout(folder)
}
} else {
OCFileListFragment.FOLDER_LAYOUT_GRID == preferences.getFolderLayout(folder)
}

fun setLayoutViewMode() {
Expand Down Expand Up @@ -102,16 +100,13 @@ class FileListLayoutManager(private val fragment: OCFileListFragment, private va
val layoutManager: RecyclerView.LayoutManager?
if (grid) {
layoutManager = GridLayoutManager(context, fragment.columnsCount)
val gridLayoutManager = layoutManager
gridLayoutManager.spanSizeLookup = object : SpanSizeLookup() {
override fun getSpanSize(position: Int): Int {
return if (position == fragment.adapter.itemCount - 1 ||
position == 0 && fragment.adapter.shouldShowHeader()
) {
gridLayoutManager.spanCount
} else {
1
}
layoutManager.spanSizeLookup = object : SpanSizeLookup() {
override fun getSpanSize(position: Int): Int = if (position == fragment.adapter.itemCount - 1 ||
(position == 0 && fragment.adapter.shouldShowHeader())
) {
layoutManager.spanCount
} else {
1
}
}
} else {
Expand Down
2 changes: 2 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1384,7 +1384,9 @@
<string name="internet_connection_required_for_encrypted_folder_setup">An internet connection is required to set up the encrypted folder</string>
<string name="prefs_setup_e2e">Set up end-to-end encryption</string>
<string name="prefs_e2e_active">End-to-end encryption is set up!</string>
<string name="prefs_remove_e2e_keys_and_files">Remove encrypted files and keys</string>
<string name="prefs_remove_e2e">Remove encryption locally</string>
<string name="remove_e2e_keys_and_files_dialog_warning">This operation will remove all encrypted files, private and public keys. Are you sure?</string>
<string name="remove_e2e">You can remove end-to-end encryption locally on this client</string>
<string name="confirm_removal">Remove local encryption</string>
<string name="remove_e2e_message">You can remove end-to-end encryption locally on this client. The encrypted files will remain on server, but will not be synced to this computer any longer.</string>
Expand Down
3 changes: 3 additions & 0 deletions app/src/main/res/xml/preferences.xml
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,9 @@
android:title="@string/prefs_remove_e2e"
android:key="remove_e2e"
android:summary="@string/remove_e2e" />
<Preference
android:title="@string/prefs_remove_e2e_keys_and_files"
android:key="remove_e2e_files_and_keys" />

<Preference
android:title="@string/prefs_help"
Expand Down
2 changes: 1 addition & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
androidCommonLibraryVersion = "0.33.2"
androidGifDrawableVersion = "1.2.31"
androidImageCropperVersion = "4.7.0"
androidLibraryVersion ="20e8cd17191f337d34b2ed97e0ac61e84a0bfc39"
androidLibraryVersion ="a1fb1a12a2"
androidPluginVersion = "9.2.0"
androidsvgVersion = "1.4"
androidxMediaVersion = "1.5.1"
Expand Down
8 changes: 8 additions & 0 deletions gradle/verification-metadata.xml
Original file line number Diff line number Diff line change
Expand Up @@ -21550,6 +21550,14 @@
<sha256 value="c310576fd498cdc27dbcbf15532d78cd3c9cca1e30708b7e23001b7d53633d15" origin="Generated by Gradle" reason="Artifact is not signed"/>
</artifact>
</component>
<component group="com.github.nextcloud" name="android-library" version="a1fb1a12a2">
<artifact name="android-library-a1fb1a12a2.aar">
<sha256 value="b4a99e1d80a646f6d8c7d9fa525be60661dcb3f766aae69ba29d5c6aeb42d9f3" origin="Generated by Gradle" reason="Artifact is not signed"/>
</artifact>
<artifact name="android-library-a1fb1a12a2.module">
<sha256 value="196c709cd6ad27ee6b27efb70ef3301a630552f76b5591d7e31784488214c340" origin="Generated by Gradle" reason="Artifact is not signed"/>
</artifact>
</component>
<component group="com.github.nextcloud" name="android-library" version="a4d86ef9d1">
<artifact name="android-library-a4d86ef9d1.aar">
<sha256 value="c6c70775d49d935e7691f43fee0ff51c2c0df03c041fd75da92cf1f0df1385a7" origin="Generated by Gradle" reason="Artifact is not signed"/>
Expand Down
Loading