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.java b/app/src/androidTest/java/com/owncloud/android/ui/dialog/DialogFragmentIT.java
index 8db216e47096..ee50970cf7ba 100644
--- a/app/src/androidTest/java/com/owncloud/android/ui/dialog/DialogFragmentIT.java
+++ b/app/src/androidTest/java/com/owncloud/android/ui/dialog/DialogFragmentIT.java
@@ -344,6 +344,11 @@ public void createFolder() {
}
+ @Override
+ public void createEncryptedFolder() {
+
+ }
+
@Override
public void uploadFromApp() {
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 92bf6bd55645..b6a8cd67ef64 100644
--- a/app/src/main/java/com/owncloud/android/datamodel/OCFile.java
+++ b/app/src/main/java/com/owncloud/android/datamodel/OCFile.java
@@ -625,6 +625,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")
@@ -632,7 +636,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 2bb9e99e4cf9..ae0bd51481af 100644
--- a/app/src/main/java/com/owncloud/android/files/FileMenuFilter.java
+++ b/app/src/main/java/com/owncloud/android/files/FileMenuFilter.java
@@ -177,7 +177,7 @@ private List filter(boolean inSingleFileFragment) {
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);
@@ -208,19 +208,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);
}
}
@@ -253,7 +255,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);
}
}
@@ -355,8 +357,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);
}
}
@@ -597,4 +601,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 f006888799f0..e27a5044c013 100644
--- a/app/src/main/java/com/owncloud/android/operations/CreateFolderOperation.java
+++ b/app/src/main/java/com/owncloud/android/operations/CreateFolderOperation.java
@@ -60,16 +60,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
@@ -105,7 +120,7 @@ protected RemoteOperationResult run(OwnCloudClient client) {
}
return new RemoteOperationResult(new IllegalStateException("E2E not supported"));
} else {
- return normalCreate(client);
+ return normalCreate(client, encrypted);
}
}
@@ -473,7 +488,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()) {
@@ -482,8 +497,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 4ccadf9d6b0a..767594c90244 100644
--- a/app/src/main/java/com/owncloud/android/services/OperationsService.java
+++ b/app/src/main/java/com/owncloud/android/services/OperationsService.java
@@ -84,6 +84,7 @@ public class OperationsService extends Service {
public static final String EXTRA_ACCOUNT = "ACCOUNT";
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";
@@ -687,7 +688,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 7cba32bb9a55..8571fd8e6779 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
@@ -61,6 +61,8 @@ open class FolderPickerActivity :
private var mSyncBroadcastReceiver: SyncBroadcastReceiver? = null
private var mSyncInProgress = false
private var mSearchOnlyFolders = false
+ private var mShowOnlyFolder = false
+ private var mHideEncryptedFolder = false
var isDoNotEnterEncryptedFolder = false
private set
@@ -114,6 +116,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) {
@@ -359,7 +364,7 @@ open class FolderPickerActivity :
}
private fun refreshListOfFilesFragment(fromSearch: Boolean) {
- listOfFilesFragment?.listDirectory(false, fromSearch)
+ listOfFilesFragment?.listDirectoryFolder(false, fromSearch, mShowOnlyFolder, mHideEncryptedFolder)
}
fun browseToRoot() {
@@ -686,6 +691,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 bde331c722a3..3b455a5c5ad8 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
@@ -467,10 +467,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 -> {
@@ -551,7 +555,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)
@@ -565,6 +569,9 @@ private void showRemoveE2EAlertDialog(PreferenceCategory preferenceCategoryMore,
preferenceCategoryMore.removePreference(pMnemonic);
}
+ // NMC: restart to show the preferences correctly
+ restartSettingsActivity();
+
dialog.dismiss();
})
.create()
@@ -1020,10 +1027,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);
- i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
- i.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
- 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);
@@ -1035,6 +1040,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) {
@@ -1052,8 +1064,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/OCFileListAdapter.java b/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListAdapter.java
index 452e366448a8..0f4d309cee52 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
@@ -76,6 +76,7 @@
import com.owncloud.android.utils.DisplayUtils;
import com.owncloud.android.utils.FileSortOrder;
import com.owncloud.android.utils.FileStorageUtils;
+import com.owncloud.android.utils.MimeType;
import com.owncloud.android.utils.MimeTypeUtil;
import com.owncloud.android.utils.theme.ViewThemeUtils;
@@ -336,6 +337,10 @@ public int getItemCount() {
@Nullable
public OCFile getItem(int position) {
+ if (position == -1) {
+ return null;
+ }
+
int newPosition = position;
if (shouldShowHeader() && position > 0) {
@@ -827,6 +832,62 @@ public void swapDirectory(
notifyDataSetChanged();
}
+ /**
+ * method will only called if only folders has to show
+ */
+ public void showOnlyFolder(
+ User account,
+ OCFile directory,
+ FileDataStorageManager updatedStorageManager,
+ boolean onlyOnDevice, String limitToMimeType,
+ boolean hideEncryptedFolder
+ ) {
+ this.onlyOnDevice = onlyOnDevice;
+
+ if (updatedStorageManager != null && !updatedStorageManager.equals(mStorageManager)) {
+ mStorageManager = updatedStorageManager;
+ ocFileListDelegate.setShowShareAvatar(true);
+ this.user = account;
+ }
+
+ if (mStorageManager != null) {
+
+ List allFiles = mStorageManager.getFolderContent(directory, onlyOnDevice);
+ mFiles.clear();
+
+ for (int i = 0; i < allFiles.size(); i++) {
+ OCFile ocFile = allFiles.get(i);
+
+ //if e2ee folder has to hide then ignore if OCFile is encrypted
+ if (hideEncryptedFolder && ocFile.isEncrypted()) {
+ continue;
+ }
+
+ if (ocFile.getMimeType().equals(MimeType.DIRECTORY)) {
+ mFiles.add(allFiles.get(i));
+ }
+ }
+
+ if (!preferences.isShowHiddenFilesEnabled()) {
+ mFiles = filterHiddenFiles(mFiles);
+ }
+ if (!limitToMimeType.isEmpty()) {
+ mFiles = filterByMimeType(mFiles, limitToMimeType);
+ }
+ FileSortOrder sortOrder = preferences.getSortOrderByFolder(directory);
+ mFiles = sortOrder.sortCloudFiles(mFiles);
+ mFilesAll.clear();
+ mFilesAll.addAll(mFiles);
+
+ currentDirectory = directory;
+ } else {
+ mFiles.clear();
+ mFilesAll.clear();
+ }
+
+ new Handler(Looper.getMainLooper()).post(this::notifyDataSetChanged);
+ }
+
/**
* Converts Offline Operations to OCFiles and adds them to the adapter for visual feedback.
* This function creates pending OCFiles, but they may not consistently appear in the UI.
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 6642b025902a..325630033c8a 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
@@ -66,6 +66,7 @@ class CreateFolderDialogFragment : DialogFragment(), DialogInterface.OnClickList
private var parentFolder: OCFile? = null
private var positiveButton: MaterialButton? = null
+ private var encrypted = false
private lateinit var binding: EditBoxDialogBinding
@@ -100,6 +101,7 @@ class CreateFolderDialogFragment : DialogFragment(), DialogInterface.OnClickList
@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)
@@ -183,7 +185,7 @@ class CreateFolderDialogFragment : DialogFragment(), DialogInterface.OnClickList
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(
@@ -201,8 +203,13 @@ class CreateFolderDialogFragment : DialogFragment(), DialogInterface.OnClickList
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.
*
@@ -210,9 +217,10 @@ class CreateFolderDialogFragment : DialogFragment(), DialogInterface.OnClickList
* @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 556dac4db784..093c06515597 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
@@ -387,6 +387,9 @@ class SyncedFolderPreferencesDialogFragment : DialogFragment(), Injectable {
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 495d230518e9..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
@@ -10,8 +10,12 @@ package com.owncloud.android.ui.events
* Event for set folder as encrypted/decrypted
*/
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 b264078da243..7f5216df9929 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
@@ -394,6 +394,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.java b/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListBottomSheetDialog.java
index dca686d638d6..555eba41f4a2 100644
--- a/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListBottomSheetDialog.java
+++ b/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListBottomSheetDialog.java
@@ -87,9 +87,8 @@ protected void onCreate(Bundle savedInstanceState) {
binding.addToCloud.setText(getContext().getResources().getString(R.string.add_to_cloud,
themeUtils.getDefaultDisplayNameForRootFolder(getContext())));
- OCCapability capability = fileActivity.getCapabilities();
- if (capability != null &&
- capability.getRichDocuments().isTrue() &&
+ OCCapability capability = fileActivity.getStorageManager().getCapability(user.getAccountName());
+ if (capability.getRichDocuments().isTrue() &&
capability.getRichDocumentsDirectEditing().isTrue() &&
capability.getRichDocumentsTemplatesAvailable().isTrue() &&
!file.isEncrypted()) {
@@ -137,6 +136,12 @@ protected void onCreate(Bundle savedInstanceState) {
binding.menuDirectCameraUpload.setVisibility(View.GONE);
}
+ if (capability.getEndToEndEncryption().isTrue() && OCFile.ROOT_PATH.equals(file.getRemotePath())) {
+ binding.menuEncryptedMkdir.setVisibility(View.VISIBLE);
+ } else {
+ binding.menuEncryptedMkdir.setVisibility(View.GONE);
+ }
+
// create rich workspace
if (editorUtils.isEditorAvailable(user,
MimeTypeUtil.MIMETYPE_TEXT_MARKDOWN) &&
@@ -177,6 +182,11 @@ private void setupClickListener() {
dismiss();
});
+ binding.menuEncryptedMkdir.setOnClickListener(v -> {
+ actions.createEncryptedFolder();
+ dismiss();
+ });
+
binding.menuUploadFromApp.setOnClickListener(v -> {
actions.uploadFromApp();
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 556cbbcc6637..cda6b5d19a8d 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
@@ -248,6 +248,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