diff --git a/app/src/androidTest/java/com/nmc/android/FileMenuFilterIT.kt b/app/src/androidTest/java/com/nmc/android/FileMenuFilterIT.kt
new file mode 100644
index 000000000000..58dc7c8386db
--- /dev/null
+++ b/app/src/androidTest/java/com/nmc/android/FileMenuFilterIT.kt
@@ -0,0 +1,152 @@
+/*
+ * Nextcloud Android client application
+ *
+ * @author Álvaro Brey Vilas
+ * Copyright (C) 2022 Álvaro Brey Vilas
+ * Copyright (C) 2022 Nextcloud GmbH
+ *
+ * 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 .
+ */
+package com.nmc.android
+
+import androidx.test.core.app.launchActivity
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.nextcloud.client.account.User
+import com.nextcloud.client.jobs.download.FileDownloadWorker
+import com.nextcloud.client.jobs.upload.FileUploadHelper
+import com.nextcloud.test.TestActivity
+import com.nextcloud.utils.EditorUtils
+import com.owncloud.android.AbstractIT
+import com.owncloud.android.R
+import com.owncloud.android.datamodel.ArbitraryDataProvider
+import com.owncloud.android.datamodel.FileDataStorageManager
+import com.owncloud.android.datamodel.OCFile
+import com.owncloud.android.files.FileMenuFilter
+import com.owncloud.android.lib.resources.status.CapabilityBooleanType
+import com.owncloud.android.lib.resources.status.OCCapability
+import com.owncloud.android.services.OperationsService
+import com.owncloud.android.ui.activity.ComponentsGetter
+import com.owncloud.android.utils.MimeType
+import io.mockk.MockKAnnotations
+import io.mockk.every
+import io.mockk.impl.annotations.MockK
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import java.security.SecureRandom
+
+@RunWith(AndroidJUnit4::class)
+class FileMenuFilterIT : AbstractIT() {
+
+ @MockK
+ private lateinit var mockComponentsGetter: ComponentsGetter
+
+ @MockK
+ private lateinit var mockStorageManager: FileDataStorageManager
+
+ @MockK
+ private lateinit var mockFileUploaderBinder: FileUploadHelper
+
+ @MockK
+ private lateinit var mockOperationsServiceBinder: OperationsService.OperationsServiceBinder
+
+ @MockK
+ private lateinit var mockFileDownloadProgressListener: FileDownloadWorker.FileDownloadProgressListener
+
+ @MockK
+ private lateinit var mockArbitraryDataProvider: ArbitraryDataProvider
+
+ private lateinit var editorUtils: EditorUtils
+
+ @Before
+ fun setup() {
+ MockKAnnotations.init(this)
+ every { mockFileUploaderBinder.isUploading(any(), any()) } returns false
+ every { mockComponentsGetter.fileUploaderHelper } returns mockFileUploaderBinder
+ every { mockFileDownloadProgressListener.isDownloading(any(), any()) } returns false
+ every { mockComponentsGetter.fileDownloadProgressListener } returns mockFileDownloadProgressListener
+ every { mockOperationsServiceBinder.isSynchronizing(any(), any()) } returns false
+ every { mockComponentsGetter.operationsServiceBinder } returns mockOperationsServiceBinder
+ every { mockStorageManager.getFileById(any()) } returns OCFile("/")
+ every { mockStorageManager.getFolderContent(any(), any()) } returns ArrayList()
+ every { mockArbitraryDataProvider.getValue(any(), any()) } returns ""
+ editorUtils = EditorUtils(mockArbitraryDataProvider)
+ }
+
+ @Test
+ fun hide_shareAndFavouriteMenu_encryptedFolder() {
+ val capability = OCCapability().apply {
+ endToEndEncryption = CapabilityBooleanType.TRUE
+ }
+
+ val encryptedFolder = OCFile("/encryptedFolder/").apply {
+ isEncrypted = true
+ mimeType = MimeType.DIRECTORY
+ fileLength = SecureRandom().nextLong()
+ }
+
+ configureCapability(capability)
+
+ launchActivity().use {
+ it.onActivity { activity ->
+ val filterFactory =
+ FileMenuFilter.Factory(mockStorageManager, activity, editorUtils)
+
+ val sut = filterFactory.newInstance(encryptedFolder, mockComponentsGetter, true, user)
+ val toHide = sut.getToHide(false)
+
+ // encrypted folder
+ assertTrue(toHide.contains(R.id.action_see_details))
+ assertTrue(toHide.contains(R.id.action_favorite))
+ assertTrue(toHide.contains(R.id.action_unset_favorite))
+ }
+ }
+ }
+
+ @Test
+ fun show_shareAndFavouriteMenu_normalFolder() {
+ val capability = OCCapability().apply {
+ endToEndEncryption = CapabilityBooleanType.TRUE
+ }
+
+ val normalFolder = OCFile("/folder/").apply {
+ mimeType = MimeType.DIRECTORY
+ fileLength = SecureRandom().nextLong()
+ }
+
+ configureCapability(capability)
+
+ launchActivity().use {
+ it.onActivity { activity ->
+ val filterFactory =
+ FileMenuFilter.Factory(mockStorageManager, activity, editorUtils)
+
+ val sut = filterFactory.newInstance(normalFolder, mockComponentsGetter, true, user)
+ val toHide = sut.getToHide(false)
+
+ // normal folder
+ assertFalse(toHide.contains(R.id.action_see_details))
+ assertFalse(toHide.contains(R.id.action_favorite))
+ assertTrue(toHide.contains(R.id.action_unset_favorite))
+ }
+ }
+ }
+
+ private fun configureCapability(capability: OCCapability) {
+ every { mockStorageManager.getCapability(any()) } returns capability
+ every { mockStorageManager.getCapability(any()) } returns capability
+ }
+}
diff --git a/app/src/androidTest/java/com/nmc/android/SetupEncryptionDialogFragmentIT.kt b/app/src/androidTest/java/com/nmc/android/SetupEncryptionDialogFragmentIT.kt
new file mode 100644
index 000000000000..89b9b638051e
--- /dev/null
+++ b/app/src/androidTest/java/com/nmc/android/SetupEncryptionDialogFragmentIT.kt
@@ -0,0 +1,56 @@
+package com.nmc.android
+
+import androidx.test.espresso.Espresso.onView
+import androidx.test.espresso.assertion.ViewAssertions.matches
+import androidx.test.espresso.intent.rule.IntentsTestRule
+import androidx.test.espresso.matcher.ViewMatchers.withHint
+import androidx.test.espresso.matcher.ViewMatchers.withId
+import androidx.test.internal.runner.junit4.statement.UiThreadStatement
+import com.nextcloud.test.TestActivity
+import com.owncloud.android.AbstractIT
+import com.owncloud.android.ui.dialog.setupEncryption.SetupEncryptionDialogFragment
+import org.junit.Rule
+import org.junit.Test
+import com.owncloud.android.R
+
+class SetupEncryptionDialogFragmentIT : AbstractIT() {
+
+ @get:Rule
+ val testActivityRule = IntentsTestRule(TestActivity::class.java, true, false)
+
+ @Test
+ fun validatePassphraseInputHint() {
+ val activity = testActivityRule.launchActivity(null)
+
+ val sut = SetupEncryptionDialogFragment.newInstance(user, 0)
+
+ sut.show(activity.supportFragmentManager, "1")
+
+ val keyWords = arrayListOf(
+ "ability",
+ "able",
+ "about",
+ "above",
+ "absent",
+ "absorb",
+ "abstract",
+ "absurd",
+ "abuse",
+ "access",
+ "accident",
+ "account",
+ "accuse"
+ )
+
+ shortSleep()
+
+ UiThreadStatement.runOnUiThread {
+ sut.setMnemonic(keyWords)
+ sut.showMnemonicInfo()
+ }
+
+ waitForIdleSync()
+
+ onView(withId(R.id.encryption_passwordInput)).check(matches(withHint("Passphrase…")))
+ }
+}
\ No newline at end of file
diff --git a/app/src/androidTest/java/com/owncloud/android/ui/dialog/DialogFragmentIT.kt b/app/src/androidTest/java/com/owncloud/android/ui/dialog/DialogFragmentIT.kt
index 9f950eac778a..52007cbbc118 100644
--- a/app/src/androidTest/java/com/owncloud/android/ui/dialog/DialogFragmentIT.kt
+++ b/app/src/androidTest/java/com/owncloud/android/ui/dialog/DialogFragmentIT.kt
@@ -431,6 +431,7 @@ class DialogFragmentIT : AbstractIT() {
val action: OCFileListBottomSheetActions = object : OCFileListBottomSheetActions {
override fun createFolder() = Unit
+ override fun createEncryptedFolder() = Unit
override fun uploadFromApp() = Unit
override fun uploadFiles() = Unit
override fun newDocument() = Unit
diff --git a/app/src/androidTest/java/com/owncloud/android/ui/fragment/OCFileListFragmentStaticServerIT.kt b/app/src/androidTest/java/com/owncloud/android/ui/fragment/OCFileListFragmentStaticServerIT.kt
index 628c28188e4b..2d0553f515cd 100644
--- a/app/src/androidTest/java/com/owncloud/android/ui/fragment/OCFileListFragmentStaticServerIT.kt
+++ b/app/src/androidTest/java/com/owncloud/android/ui/fragment/OCFileListFragmentStaticServerIT.kt
@@ -380,13 +380,17 @@ class OCFileListFragmentStaticServerIT : AbstractIT() {
testFolder.richWorkspace = " "
activity.storageManager.saveFile(testFolder)
- sut.adapter.swapDirectory(user, testFolder, activity.storageManager, false, "")
+ sut.adapter.swapDirectory(user, testFolder, activity.storageManager, false, "",
+ showOnlyFolder = false,
+ hideEncryptedFolder = false)
Assert.assertFalse(sut.adapter.shouldShowHeader())
testFolder.richWorkspace = null
activity.storageManager.saveFile(testFolder)
- sut.adapter.swapDirectory(user, testFolder, activity.storageManager, false, "")
+ sut.adapter.swapDirectory(user, testFolder, activity.storageManager, false, "",
+ showOnlyFolder = false,
+ hideEncryptedFolder = false)
Assert.assertFalse(sut.adapter.shouldShowHeader())
EspressoIdlingResource.increment()
diff --git a/app/src/main/java/com/nextcloud/utils/extensions/OCFileExtensions.kt b/app/src/main/java/com/nextcloud/utils/extensions/OCFileExtensions.kt
index 1108e7572f65..9c590a2dee28 100644
--- a/app/src/main/java/com/nextcloud/utils/extensions/OCFileExtensions.kt
+++ b/app/src/main/java/com/nextcloud/utils/extensions/OCFileExtensions.kt
@@ -80,3 +80,7 @@ fun OCFile?.getDepth(): OCFileDepth? {
// Otherwise, it's a subdirectory of a subdirectory
return DeepLevel
}
+
+// NMC method to filter only folders with/without e2ee folders
+fun List.filterByFolder(hideEncryptedFolder: Boolean = false): List =
+ filter { it.isFolder && (!hideEncryptedFolder || !it.isEncrypted) }
\ No newline at end of file
diff --git a/app/src/main/java/com/owncloud/android/datamodel/OCFile.java b/app/src/main/java/com/owncloud/android/datamodel/OCFile.java
index 11455a2707d2..d4ba3708063a 100644
--- a/app/src/main/java/com/owncloud/android/datamodel/OCFile.java
+++ b/app/src/main/java/com/owncloud/android/datamodel/OCFile.java
@@ -623,6 +623,10 @@ public boolean isHidden() {
}
/**
+ * The unique fileId for the file within the instance This only works if we have 12 digits for instanceId RemoteId
+ * is a combination of localId + instanceId If a file has remoteId: 4174305739oc97a8ddfc96, in this 4174305739 is
+ * localId & oc97a8ddfc96 is instanceId which is of 12 digits
+ *
* unique fileId for the file within the instance
*/
@SuppressFBWarnings("STT")
@@ -630,7 +634,8 @@ public long getLocalId() {
if (localId > 0) {
return localId;
} else if (remoteId != null && remoteId.length() > 8) {
- return Long.parseLong(remoteId.substring(0, 8).replaceAll("^0*", ""));
+ //NMC Customization --> for long remote id's
+ return Long.parseLong(remoteId.substring(0, remoteId.length() - 12).replaceAll("^0*", ""));
} else {
return -1;
}
diff --git a/app/src/main/java/com/owncloud/android/files/FileMenuFilter.java b/app/src/main/java/com/owncloud/android/files/FileMenuFilter.java
index 6665598c0a4b..685984951db5 100644
--- a/app/src/main/java/com/owncloud/android/files/FileMenuFilter.java
+++ b/app/src/main/java/com/owncloud/android/files/FileMenuFilter.java
@@ -185,7 +185,7 @@ private void filterPermissionActions(List toHide) {
private void filterShareFile(List toHide, OCCapability capability) {
- if (!isSingleSelection() || containsEncryptedFile() || hasEncryptedParent() ||
+ if (!isSingleSelection() || containsEncryptedFile() || hasEncryptedParent() || containsEncryptedFolder() ||
(!isShareViaLinkAllowed() && !isShareWithUsersAllowed()) ||
!isShareApiEnabled(capability) || !files.iterator().next().canReshare()) {
toHide.add(R.id.action_send_share_file);
@@ -216,19 +216,21 @@ private void filterSendFiles(List toHide, boolean inSingleFileFragment)
}
private void filterDetails(Collection toHide) {
- if (!isSingleSelection()) {
+ if (!isSingleSelection() || containsEncryptedFolder() || containsEncryptedFile()) {
toHide.add(R.id.action_see_details);
}
}
private void filterFavorite(List toHide, boolean synchronizing) {
- if (files.isEmpty() || synchronizing || allFavorites()) {
+ if (files.isEmpty() || synchronizing || allFavorites() || containsEncryptedFile()
+ || containsEncryptedFolder()) {
toHide.add(R.id.action_favorite);
}
}
private void filterUnfavorite(List toHide, boolean synchronizing) {
- if (files.isEmpty() || synchronizing || allNotFavorites()) {
+ if (files.isEmpty() || synchronizing || allNotFavorites() || containsEncryptedFile()
+ || containsEncryptedFolder()) {
toHide.add(R.id.action_unset_favorite);
}
}
@@ -261,7 +263,7 @@ private void filterUnlock(List toHide, boolean fileLockingEnabled) {
private void filterEncrypt(List toHide, boolean endToEndEncryptionEnabled) {
if (files.isEmpty() || !isSingleSelection() || isSingleFile() || isEncryptedFolder() || isGroupFolder()
- || !endToEndEncryptionEnabled || !isEmptyFolder() || isShared()) {
+ || !endToEndEncryptionEnabled || !isEmptyFolder() || isShared() || isInSubFolder()) {
toHide.add(R.id.action_encrypted);
}
}
@@ -363,8 +365,10 @@ private void filterSelectAll(List toHide, boolean inSingleFileFragment)
}
private void filterRemove(List toHide, boolean synchronizing) {
- if (files.isEmpty() || synchronizing || containsLockedFile()
- || containsEncryptedFolder() || isFolderAndContainsEncryptedFile()) {
+ if ((files.isEmpty() || synchronizing || containsLockedFile()
+ || containsEncryptedFolder() || isFolderAndContainsEncryptedFile())
+ //show delete option for encrypted sub-folder
+ && !hasEncryptedParent()) {
toHide.add(R.id.action_remove_file);
}
}
@@ -605,4 +609,15 @@ private boolean isShared() {
}
return false;
}
+
+ private boolean isInSubFolder() {
+ OCFile folder = files.iterator().next();
+ OCFile parent = storageManager.getFileById(folder.getParentId());
+
+ if (parent == null) {
+ return false;
+ }
+
+ return !OCFile.ROOT_PATH.equals(parent.getRemotePath());
+ }
}
diff --git a/app/src/main/java/com/owncloud/android/operations/CreateFolderOperation.java b/app/src/main/java/com/owncloud/android/operations/CreateFolderOperation.java
index c2d6e3930ff0..b5b715126b09 100644
--- a/app/src/main/java/com/owncloud/android/operations/CreateFolderOperation.java
+++ b/app/src/main/java/com/owncloud/android/operations/CreateFolderOperation.java
@@ -61,16 +61,31 @@ public class CreateFolderOperation extends SyncOperation implements OnRemoteOper
private RemoteFile createdRemoteFolder;
private User user;
private Context context;
+ private boolean encrypted;
/**
* Constructor
*/
- public CreateFolderOperation(String remotePath, User user, Context context, FileDataStorageManager storageManager) {
+ public CreateFolderOperation(String remotePath,
+ User user,
+ Context context,
+ FileDataStorageManager storageManager
+ ) {
+ this(remotePath, false, user, context, storageManager);
+ }
+
+ public CreateFolderOperation(String remotePath,
+ boolean encrypted,
+ User user,
+ Context context,
+ FileDataStorageManager storageManager
+ ) {
super(storageManager);
this.remotePath = remotePath;
this.user = user;
this.context = context;
+ this.encrypted = encrypted;
}
@Override
@@ -106,7 +121,7 @@ protected RemoteOperationResult run(OwnCloudClient client) {
}
return new RemoteOperationResult(new IllegalStateException("E2E not supported"));
} else {
- return normalCreate(client);
+ return normalCreate(client, encrypted);
}
}
@@ -490,7 +505,7 @@ private String createRandomFileName(DecryptedFolderMetadataFileV1 metadata) {
return encryptedFileName;
}
- private RemoteOperationResult normalCreate(OwnCloudClient client) {
+ private RemoteOperationResult normalCreate(OwnCloudClient client, boolean encrypted) {
RemoteOperationResult result = new CreateFolderRemoteOperation(remotePath, true).execute(client);
if (result.isSuccess()) {
@@ -499,8 +514,118 @@ private RemoteOperationResult normalCreate(OwnCloudClient client) {
createdRemoteFolder = (RemoteFile) remoteFolderOperationResult.getData().get(0);
saveFolderInDB();
- } else {
- Log_OC.e(TAG, remotePath + " hasn't been created");
+
+ if (encrypted) {
+ final OCFile folder = getStorageManager().getFileByDecryptedRemotePath(remotePath);
+
+ final RemoteOperationResult remoteOperationResult =
+ new ToggleEncryptionRemoteOperation(folder.getLocalId(),
+ remotePath,
+ true)
+ .execute(client);
+
+ if (remoteOperationResult.isSuccess()) {
+
+ ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProviderImpl(context);
+ String privateKey = arbitraryDataProvider.getValue(user.getAccountName(), EncryptionUtils.PRIVATE_KEY);
+ String publicKey = arbitraryDataProvider.getValue(user.getAccountName(), EncryptionUtils.PUBLIC_KEY);
+ String token = null;
+ E2EVersion e2EVersion = getStorageManager().getCapability(user.getAccountName()).getEndToEndEncryptionApiVersion();
+
+ try {
+ // lock folder
+ token = EncryptionUtils.lockFolder(folder, client);
+
+ if (e2EVersion == E2EVersion.V2_0) {
+ // Update metadata
+ Pair metadataPair = EncryptionUtils.retrieveMetadata(folder,
+ client,
+ privateKey,
+ publicKey,
+ getStorageManager(),
+ user,
+ context,
+ arbitraryDataProvider);
+
+ boolean metadataExists = metadataPair.first;
+ DecryptedFolderMetadataFile metadata = metadataPair.second;
+
+ new EncryptionUtilsV2().serializeAndUploadMetadata(folder,
+ metadata,
+ token,
+ client,
+ metadataExists,
+ context,
+ user,
+ getStorageManager());
+
+ // unlock folder
+ RemoteOperationResult unlockFolderResult = EncryptionUtils.unlockFolder(folder, client, token);
+
+ if (unlockFolderResult.isSuccess()) {
+ token = null;
+ } else {
+ // TODO E2E: do better
+ throw new RuntimeException("Could not unlock folder!");
+ }
+
+ } else if (e2EVersion == E2EVersion.V1_0 ||
+ e2EVersion == E2EVersion.V1_1 ||
+ e2EVersion == E2EVersion.V1_2
+ ) {
+ // unlock folder
+ RemoteOperationResult unlockFolderResult = EncryptionUtils.unlockFolderV1(folder, client, token);
+
+ if (unlockFolderResult.isSuccess()) {
+ token = null;
+ } else {
+ // TODO E2E: do better
+ throw new RuntimeException("Could not unlock folder!");
+ }
+ } else if (e2EVersion == E2EVersion.UNKNOWN) {
+ return new RemoteOperationResult(new IllegalStateException("E2E not supported"));
+ }
+
+ folder.setEncrypted(true);
+ getStorageManager().saveFile(folder);
+ } catch (Throwable e) {
+ if (token != null) {
+ if (e2EVersion == E2EVersion.V2_0) {
+ if (!EncryptionUtils.unlockFolder(folder, client, token).isSuccess()) {
+ throw new RuntimeException("Could not clean up after failing folder creation!", e);
+ }
+ } else if (e2EVersion == E2EVersion.V1_0 ||
+ e2EVersion == E2EVersion.V1_1 ||
+ e2EVersion == E2EVersion.V1_2) {
+ if (!EncryptionUtils.unlockFolderV1(folder, client, token).isSuccess()) {
+ throw new RuntimeException("Could not clean up after failing folder creation!", e);
+ }
+ }
+ }
+ // TODO E2E: do better
+ return new RemoteOperationResult(new Exception(e));
+ } finally {
+ // unlock folder
+ if (token != null) {
+ RemoteOperationResult unlockFolderResult = null;
+ if (e2EVersion == E2EVersion.V2_0) {
+ unlockFolderResult = EncryptionUtils.unlockFolder(folder, client, token);
+ } else if (e2EVersion == E2EVersion.V1_0 ||
+ e2EVersion == E2EVersion.V1_1 ||
+ e2EVersion == E2EVersion.V1_2) {
+ unlockFolderResult = EncryptionUtils.unlockFolderV1(folder, client, token);
+ }
+
+ if (unlockFolderResult != null && !unlockFolderResult.isSuccess()) {
+ // TODO E2E: do better
+ throw new RuntimeException("Could not unlock folder!");
+ }
+ }
+ }
+ }
+ } else {
+ Log_OC.e(TAG, remotePath + " hasn't been created");
+ }
}
return result;
diff --git a/app/src/main/java/com/owncloud/android/services/OperationsService.java b/app/src/main/java/com/owncloud/android/services/OperationsService.java
index 6923606819af..79ffdf92499b 100644
--- a/app/src/main/java/com/owncloud/android/services/OperationsService.java
+++ b/app/src/main/java/com/owncloud/android/services/OperationsService.java
@@ -85,6 +85,7 @@ public class OperationsService extends Service {
public static final String EXTRA_POST_DIALOG_EVENT = "EXTRA_POST_DIALOG_EVENT";
public static final String EXTRA_SERVER_URL = "SERVER_URL";
public static final String EXTRA_REMOTE_PATH = "REMOTE_PATH";
+ public static final String EXTRA_ENCRYPTED = "ENCRYPTED";
public static final String EXTRA_NEWNAME = "NEWNAME";
public static final String EXTRA_REMOVE_ONLY_LOCAL = "REMOVE_LOCAL_COPY";
public static final String EXTRA_SYNC_FILE_CONTENTS = "SYNC_FILE_CONTENTS";
@@ -708,7 +709,9 @@ private Pair newOperation(Intent operationIntent) {
case ACTION_CREATE_FOLDER:
remotePath = operationIntent.getStringExtra(EXTRA_REMOTE_PATH);
+ boolean encrypted = operationIntent.getBooleanExtra(EXTRA_ENCRYPTED, false);
operation = new CreateFolderOperation(remotePath,
+ encrypted,
user,
getApplicationContext(),
fileDataStorageManager);
diff --git a/app/src/main/java/com/owncloud/android/ui/activity/FolderPickerActivity.kt b/app/src/main/java/com/owncloud/android/ui/activity/FolderPickerActivity.kt
index 3730796b21ae..8d09e35aeb38 100644
--- a/app/src/main/java/com/owncloud/android/ui/activity/FolderPickerActivity.kt
+++ b/app/src/main/java/com/owncloud/android/ui/activity/FolderPickerActivity.kt
@@ -65,6 +65,8 @@ open class FolderPickerActivity :
private var mSyncBroadcastReceiver: SyncBroadcastReceiver? = null
private var mSearchOnlyFolders = false
+ private var mShowOnlyFolder = false
+ private var mHideEncryptedFolder = false
var isDoNotEnterEncryptedFolder = false
private set
@@ -124,6 +126,9 @@ open class FolderPickerActivity :
}
private fun setupAction() {
+ mShowOnlyFolder = intent.getBooleanExtra(EXTRA_SHOW_ONLY_FOLDER, false)
+ mHideEncryptedFolder = intent.getBooleanExtra(EXTRA_HIDE_ENCRYPTED_FOLDER, false)
+
action = intent.getStringExtra(EXTRA_ACTION)
if (action != null && action == CHOOSE_LOCATION) {
@@ -352,7 +357,7 @@ open class FolderPickerActivity :
}
private fun refreshListOfFilesFragment(fromSearch: Boolean) {
- listOfFilesFragment?.listDirectory(false, fromSearch)
+ listOfFilesFragment?.listDirectoryFolder(false, fromSearch, mShowOnlyFolder, mHideEncryptedFolder)
}
fun browseToRoot() {
@@ -668,6 +673,12 @@ open class FolderPickerActivity :
@JvmField
val EXTRA_ACTION = FolderPickerActivity::class.java.canonicalName?.plus(".EXTRA_ACTION")
+ @JvmField
+ val EXTRA_SHOW_ONLY_FOLDER = FolderPickerActivity::class.java.canonicalName?.plus(".EXTRA_SHOW_ONLY_FOLDER")
+
+ @JvmField
+ val EXTRA_HIDE_ENCRYPTED_FOLDER = FolderPickerActivity::class.java.canonicalName?.plus(".EXTRA_HIDE_ENCRYPTED_FOLDER")
+
const val MOVE_OR_COPY = "MOVE_OR_COPY"
const val CHOOSE_LOCATION = "CHOOSE_LOCATION"
private val TAG = FolderPickerActivity::class.java.simpleName
diff --git a/app/src/main/java/com/owncloud/android/ui/activity/SettingsActivity.java b/app/src/main/java/com/owncloud/android/ui/activity/SettingsActivity.java
index ffdbfd72ae9a..d7041ef32841 100644
--- a/app/src/main/java/com/owncloud/android/ui/activity/SettingsActivity.java
+++ b/app/src/main/java/com/owncloud/android/ui/activity/SettingsActivity.java
@@ -488,10 +488,14 @@ private void setupE2EPreference(PreferenceCategory preferenceCategoryMore) {
Preference preference = findPreference("setup_e2e");
if (preference != null) {
+
+ if (!CapabilityUtils.getCapability(this).getEndToEndEncryption().isTrue()) {
+ preferenceCategoryMore.removePreference(preference);
+ return;
+ }
+
if (FileOperationsHelper.isEndToEndEncryptionSetup(this, user) ||
- CapabilityUtils.getCapability(this).getEndToEndEncryptionKeysExist().isTrue() ||
- CapabilityUtils.getCapability(this).getEndToEndEncryptionKeysExist().isUnknown()
- ) {
+ CapabilityUtils.getCapability(this).getEndToEndEncryptionKeysExist().isTrue()) {
preferenceCategoryMore.removePreference(preference);
} else {
preference.setOnPreferenceClickListener(p -> {
@@ -572,7 +576,7 @@ private void removeE2E(PreferenceCategory preferenceCategoryMore) {
}
private void showRemoveE2EAlertDialog(PreferenceCategory preferenceCategoryMore, Preference preference) {
- new MaterialAlertDialogBuilder(this, R.style.FallbackTheming_Dialog)
+ new MaterialAlertDialogBuilder(this)
.setTitle(R.string.prefs_e2e_mnemonic)
.setMessage(getString(R.string.remove_e2e_message))
.setCancelable(true)
@@ -586,6 +590,9 @@ private void showRemoveE2EAlertDialog(PreferenceCategory preferenceCategoryMore,
preferenceCategoryMore.removePreference(pMnemonic);
}
+ // NMC: restart to show the preferences correctly
+ restartSettingsActivity();
+
dialog.dismiss();
})
.create()
@@ -1082,8 +1089,8 @@ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
} else if (requestCode == ACTION_SHOW_MNEMONIC && resultCode == RESULT_OK) {
handleMnemonicRequest(data);
} else if (requestCode == ACTION_E2E && data != null && data.getBooleanExtra(SetupEncryptionDialogFragment.SUCCESS, false)) {
- Intent i = new Intent(this, SettingsActivity.class);
- startActivity(i);
+ //restart to show the preferences correctly
+ restartSettingsActivity();
} else if (requestCode == ACTION_SET_STORAGE_LOCATION && data != null) {
String newPath = data.getStringExtra(ChooseStorageLocationActivity.KEY_RESULT_STORAGE_LOCATION);
@@ -1098,6 +1105,13 @@ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
}
}
+ private void restartSettingsActivity() {
+ Intent i = new Intent(this, SettingsActivity.class);
+ i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ i.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
+ startActivity(i);
+ }
+
@VisibleForTesting
public void handleMnemonicRequest(Intent data) {
if (data == null) {
@@ -1115,8 +1129,8 @@ public void handleMnemonicRequest(Intent data) {
}
private void showMnemonicAlertDialogDialog(String mnemonic) {
- new MaterialAlertDialogBuilder(this, R.style.FallbackTheming_Dialog)
- .setTitle(R.string.prefs_e2e_mnemonic)
+ new MaterialAlertDialogBuilder(this)
+ .setTitle(R.string.dialog_e2e_mnemonic_title)
.setMessage(mnemonic)
.setPositiveButton(R.string.common_ok, (dialog, which) -> dialog.dismiss())
.setNegativeButton(R.string.common_cancel, (dialog, i) -> dialog.dismiss())
diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/CommonOCFileListAdapterInterface.kt b/app/src/main/java/com/owncloud/android/ui/adapter/CommonOCFileListAdapterInterface.kt
index 314688f7283e..e44e2d1a555c 100644
--- a/app/src/main/java/com/owncloud/android/ui/adapter/CommonOCFileListAdapterInterface.kt
+++ b/app/src/main/java/com/owncloud/android/ui/adapter/CommonOCFileListAdapterInterface.kt
@@ -22,7 +22,9 @@ interface CommonOCFileListAdapterInterface {
directory: OCFile,
storageManager: FileDataStorageManager,
onlyOnDevice: Boolean,
- mLimitToMimeType: String
+ mLimitToMimeType: String,
+ showOnlyFolder: Boolean,
+ hideEncryptedFolder: Boolean
)
fun setHighlightedItem(file: OCFile)
diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/GalleryAdapter.kt b/app/src/main/java/com/owncloud/android/ui/adapter/GalleryAdapter.kt
index 0c84fe6c84e3..8b34ee3754a6 100644
--- a/app/src/main/java/com/owncloud/android/ui/adapter/GalleryAdapter.kt
+++ b/app/src/main/java/com/owncloud/android/ui/adapter/GalleryAdapter.kt
@@ -366,7 +366,9 @@ class GalleryAdapter(
directory: OCFile,
storageManager: FileDataStorageManager,
onlyOnDevice: Boolean,
- mLimitToMimeType: String
+ mLimitToMimeType: String,
+ showOnlyFolder: Boolean,
+ hideEncryptedFolder: Boolean
) = Unit
override fun setHighlightedItem(file: OCFile) = Unit
diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListAdapter.java b/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListAdapter.java
index d8471ec1e2c2..721a22cad991 100644
--- a/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListAdapter.java
+++ b/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListAdapter.java
@@ -347,6 +347,10 @@ public int getItemCount() {
@Nullable
public OCFile getItem(int position) {
+ if (position == -1) {
+ return null;
+ }
+
int newPosition = position;
if (shouldShowHeader() && position > 0) {
@@ -831,7 +835,9 @@ public void swapDirectory(
@NonNull OCFile directory,
@NonNull FileDataStorageManager updatedStorageManager,
boolean onlyOnDevice,
- @NonNull String limitToMimeType) {
+ @NonNull String limitToMimeType,
+ boolean showOnlyFolder,
+ boolean hideEncryptedFolder) {
this.onlyOnDevice = onlyOnDevice;
@@ -851,6 +857,8 @@ public void swapDirectory(
adapterDataProvider,
onlyOnDevice,
limitToMimeType,
+ showOnlyFolder,
+ hideEncryptedFolder,
preferences,
userId,
(newList, fileSortOrder) ->
diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/helper/OCFileListAdapterHelper.kt b/app/src/main/java/com/owncloud/android/ui/adapter/helper/OCFileListAdapterHelper.kt
index 9d1fd774e87f..ce3a87edd562 100644
--- a/app/src/main/java/com/owncloud/android/ui/adapter/helper/OCFileListAdapterHelper.kt
+++ b/app/src/main/java/com/owncloud/android/ui/adapter/helper/OCFileListAdapterHelper.kt
@@ -35,6 +35,8 @@ class OCFileListAdapterHelper {
dataProvider: OCFileListAdapterDataProvider,
onlyOnDevice: Boolean,
limitToMimeType: String,
+ showOnlyFolder: Boolean,
+ hideEncryptedFolder: Boolean,
preferences: AppPreferences,
userId: String,
onComplete: (List, FileSortOrder) -> Unit
@@ -45,6 +47,8 @@ class OCFileListAdapterHelper {
dataProvider,
onlyOnDevice,
limitToMimeType,
+ showOnlyFolder,
+ hideEncryptedFolder,
preferences,
userId
)
@@ -59,6 +63,8 @@ class OCFileListAdapterHelper {
dataProvider: OCFileListAdapterDataProvider,
onlyOnDevice: Boolean,
limitToMimeType: String,
+ showOnlyFolder: Boolean,
+ hideEncryptedFolder: Boolean,
preferences: AppPreferences,
userId: String
): Pair, FileSortOrder> {
@@ -72,6 +78,11 @@ class OCFileListAdapterHelper {
val filtered = ArrayList(rawResult.size)
for (file in rawResult) {
+ // NMC filter condition to show only folder with or without encrypted folders
+ if (showOnlyFolder && (!file.isFolder && (hideEncryptedFolder || file.isEncrypted))) {
+ continue
+ }
+
if (!showHiddenFiles && file.isHidden) {
continue
}
diff --git a/app/src/main/java/com/owncloud/android/ui/dialog/CreateFolderDialogFragment.kt b/app/src/main/java/com/owncloud/android/ui/dialog/CreateFolderDialogFragment.kt
index f3078dce73cb..0142bed2900e 100644
--- a/app/src/main/java/com/owncloud/android/ui/dialog/CreateFolderDialogFragment.kt
+++ b/app/src/main/java/com/owncloud/android/ui/dialog/CreateFolderDialogFragment.kt
@@ -70,6 +70,7 @@ class CreateFolderDialogFragment :
private var parentFolder: OCFile? = null
private var positiveButton: MaterialButton? = null
+ private var encrypted = false
private lateinit var binding: EditBoxDialogBinding
@@ -104,6 +105,7 @@ class CreateFolderDialogFragment :
@Suppress("EmptyFunctionBlock")
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
parentFolder = arguments?.getParcelableArgument(ARG_PARENT_FOLDER, OCFile::class.java)
+ encrypted = arguments?.getBoolean(ARG_ENCRYPTED) ?: false
val inflater = requireActivity().layoutInflater
binding = EditBoxDialogBinding.inflate(inflater, null, false)
@@ -190,7 +192,7 @@ class CreateFolderDialogFragment :
val path = parentFolder?.decryptedRemotePath + newFolderName + OCFile.PATH_SEPARATOR
connectivityService.isNetworkAndServerAvailable { result ->
if (result) {
- typedActivity()?.fileOperationsHelper?.createFolder(path)
+ typedActivity()?.fileOperationsHelper?.createFolder(path, encrypted)
} else {
Log_OC.d(TAG, "Network not available, creating offline operation")
fileDataStorageManager.addCreateFolderOfflineOperation(
@@ -208,8 +210,13 @@ class CreateFolderDialogFragment :
companion object {
private const val TAG = "CreateFolderDialogFragment"
private const val ARG_PARENT_FOLDER = "PARENT_FOLDER"
+ private const val ARG_ENCRYPTED = "ENCRYPTED"
const val CREATE_FOLDER_FRAGMENT = "CREATE_FOLDER_FRAGMENT"
+ @JvmStatic
+ fun newInstance(parentFolder: OCFile?): CreateFolderDialogFragment {
+ return newInstance(parentFolder, false)
+ }
/**
* Public factory method to create new CreateFolderDialogFragment instances.
*
@@ -217,9 +224,10 @@ class CreateFolderDialogFragment :
* @return Dialog ready to show.
*/
@JvmStatic
- fun newInstance(parentFolder: OCFile?): CreateFolderDialogFragment {
+ fun newInstance(parentFolder: OCFile?, encrypted: Boolean): CreateFolderDialogFragment {
val bundle = Bundle().apply {
putParcelable(ARG_PARENT_FOLDER, parentFolder)
+ putBoolean(ARG_ENCRYPTED, encrypted)
}
return CreateFolderDialogFragment().apply {
diff --git a/app/src/main/java/com/owncloud/android/ui/dialog/SyncedFolderPreferencesDialogFragment.kt b/app/src/main/java/com/owncloud/android/ui/dialog/SyncedFolderPreferencesDialogFragment.kt
index 6656d0beb144..6c1d80861cc1 100644
--- a/app/src/main/java/com/owncloud/android/ui/dialog/SyncedFolderPreferencesDialogFragment.kt
+++ b/app/src/main/java/com/owncloud/android/ui/dialog/SyncedFolderPreferencesDialogFragment.kt
@@ -389,6 +389,9 @@ class SyncedFolderPreferencesDialogFragment :
binding.remoteFolderContainer.setOnClickListener {
val action = Intent(activity, FolderPickerActivity::class.java).apply {
putExtra(FolderPickerActivity.EXTRA_ACTION, FolderPickerActivity.CHOOSE_LOCATION)
+ // NMC Customization
+ putExtra(FolderPickerActivity.EXTRA_SHOW_ONLY_FOLDER, true)
+ putExtra(FolderPickerActivity.EXTRA_HIDE_ENCRYPTED_FOLDER, true)
}
requireActivity().startActivityForResult(action, REQUEST_CODE__SELECT_REMOTE_FOLDER)
}
diff --git a/app/src/main/java/com/owncloud/android/ui/events/EncryptionEvent.kt b/app/src/main/java/com/owncloud/android/ui/events/EncryptionEvent.kt
index 57bd97bf5caa..f49b8e169fbb 100644
--- a/app/src/main/java/com/owncloud/android/ui/events/EncryptionEvent.kt
+++ b/app/src/main/java/com/owncloud/android/ui/events/EncryptionEvent.kt
@@ -9,4 +9,13 @@ package com.owncloud.android.ui.events
/**
* Event for set folder as encrypted/decrypted
*/
-class EncryptionEvent(val localId: Long, val remoteId: String, val remotePath: String, val shouldBeEncrypted: Boolean)
+class EncryptionEvent(
+ @JvmField
+ val localId: Long,
+ @JvmField
+ val remoteId: String,
+ @JvmField
+ val remotePath: String,
+ @JvmField
+ val shouldBeEncrypted: Boolean
+)
diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/GalleryFragment.java b/app/src/main/java/com/owncloud/android/ui/fragment/GalleryFragment.java
index 094c03da6464..62c90d8dee86 100644
--- a/app/src/main/java/com/owncloud/android/ui/fragment/GalleryFragment.java
+++ b/app/src/main/java/com/owncloud/android/ui/fragment/GalleryFragment.java
@@ -393,6 +393,8 @@ public void updateMediaContent(GalleryFragmentBottomSheetDialog.MediaState media
public void selectMediaFolder() {
Intent action = new Intent(requireActivity(), FolderPickerActivity.class);
action.putExtra(FolderPickerActivity.EXTRA_ACTION, FolderPickerActivity.CHOOSE_LOCATION);
+ action.putExtra(FolderPickerActivity.EXTRA_SHOW_ONLY_FOLDER, true);
+ action.putExtra(FolderPickerActivity.EXTRA_HIDE_ENCRYPTED_FOLDER, true);
startActivityForResult(action, SELECT_LOCATION_REQUEST_CODE);
}
diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListBottomSheetActions.java b/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListBottomSheetActions.java
index c28f1e9837f9..4b4bc13a9940 100644
--- a/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListBottomSheetActions.java
+++ b/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListBottomSheetActions.java
@@ -18,6 +18,11 @@ public interface OCFileListBottomSheetActions {
*/
void createFolder();
+ /**
+ * creates an encrypted folder within the actual folder
+ */
+ void createEncryptedFolder();
+
/**
* offers a file upload with the Android OS file picker to the current folder.
*/
diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListBottomSheetDialog.kt b/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListBottomSheetDialog.kt
index d67689722a45..8f8f9205b99b 100644
--- a/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListBottomSheetDialog.kt
+++ b/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListBottomSheetDialog.kt
@@ -112,6 +112,24 @@ class OCFileListBottomSheetDialog(
) {
binding.templates.visibility = View.VISIBLE
}
+
+ if (capability != null && capability.endToEndEncryption.isTrue) {
+ // NMC-4348 fix
+ // show encrypted folder option for root and e2ee folder
+ binding.menuEncryptedMkdir.visibility =
+ if (file.isEncrypted
+ || OCFile.ROOT_PATH == file.remotePath
+ )
+ View.VISIBLE
+ else
+ View.GONE
+ // for e2ee folder don't show normal folder option
+ if (file.isEncrypted) {
+ binding.menuMkdir.visibility = View.GONE
+ }
+ } else {
+ binding.menuEncryptedMkdir.visibility = View.GONE
+ }
}
@Suppress("DEPRECATION")
@@ -194,6 +212,18 @@ class OCFileListBottomSheetDialog(
dismiss()
}
+ binding.menuEncryptedMkdir.setOnClickListener {
+ // NMC-4348 fix
+ // for e2ee folder call normal folder creation
+ // it will auto handle creating e2ee sub folder
+ if (file.isEncrypted) {
+ actions.createFolder()
+ } else {
+ actions.createEncryptedFolder()
+ }
+ dismiss()
+ }
+
menuDirectCameraUpload.setOnClickListener {
actions.directCameraUpload()
dismiss()
diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java b/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java
index 25a1d1a0b192..6d62ba04fdb9 100644
--- a/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java
+++ b/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java
@@ -239,6 +239,8 @@ protected enum MenuItemAddRemove {
ADD_GRID_AND_SORT_WITH_SEARCH
}
+ private boolean mShowOnlyFolder, mHideEncryptedFolder;
+
protected MenuItemAddRemove menuItemAddRemoveValue = MenuItemAddRemove.ADD_GRID_AND_SORT_WITH_SEARCH;
private List