Răsfoiți Sursa

E2E overhaul
- Limit online editing/stream when e2e-encrypted file
- download files in subfolder to correct local folder name
- use httpd/unix-directory for folder in metadata
- set encryption status of subfolder

Signed-off-by: nextcloud-android-bot <android@nextcloud.com>
Signed-off-by: tobiasKaminsky <tobias@kaminsky.me>

tobiasKaminsky 5 ani în urmă
părinte
comite
a2fc279c01
55 a modificat fișierele cu 1403 adăugiri și 426 ștergeri
  1. 2 0
      .drone.yml
  2. 1 0
      build.gradle
  3. 1 1
      scripts/analysis/findbugs-results.txt
  4. 2 1
      spotbugs-filter.xml
  5. 384 0
      src/androidTest/java/com/nextcloud/client/EndToEndRandomIT.java
  6. 3 2
      src/androidTest/java/com/nextcloud/client/FileDisplayActivityIT.java
  7. 96 6
      src/androidTest/java/com/owncloud/android/AbstractIT.java
  8. 14 6
      src/androidTest/java/com/owncloud/android/FileIT.java
  9. 1 1
      src/androidTest/java/com/owncloud/android/ScreenshotsIT.java
  10. 36 66
      src/androidTest/java/com/owncloud/android/UploadIT.java
  11. 4 4
      src/androidTest/java/com/owncloud/android/ui/activity/FolderPickerActivityIT.java
  12. 13 5
      src/androidTest/java/com/owncloud/android/ui/fragment/OCFileListFragmentIT.kt
  13. 183 45
      src/androidTest/java/com/owncloud/android/util/EncryptionTestIT.java
  14. 2 1
      src/androidTest/java/com/owncloud/android/util/ErrorMessageAdapterIT.java
  15. 1 0
      src/main/java/com/nextcloud/client/account/CurrentAccountProvider.java
  16. 1 1
      src/main/java/com/nextcloud/client/jobs/OfflineSyncWork.kt
  17. 40 25
      src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java
  18. 73 35
      src/main/java/com/owncloud/android/datamodel/OCFile.java
  19. 2 2
      src/main/java/com/owncloud/android/datamodel/ThumbnailsCacheManager.java
  20. 2 1
      src/main/java/com/owncloud/android/db/ProviderMeta.java
  21. 1 1
      src/main/java/com/owncloud/android/files/FileMenuFilter.java
  22. 2 2
      src/main/java/com/owncloud/android/files/services/FileDownloader.java
  23. 0 0
      src/main/java/com/owncloud/android/jobs/OfflineSyncJob.java
  24. 215 30
      src/main/java/com/owncloud/android/operations/CreateFolderOperation.java
  25. 6 2
      src/main/java/com/owncloud/android/operations/DownloadFileOperation.java
  26. 32 8
      src/main/java/com/owncloud/android/operations/RefreshFolderOperation.java
  27. 13 15
      src/main/java/com/owncloud/android/operations/RemoveFileOperation.java
  28. 27 7
      src/main/java/com/owncloud/android/operations/RemoveRemoteEncryptedFileOperation.java
  29. 6 3
      src/main/java/com/owncloud/android/operations/RenameFileOperation.java
  30. 0 1
      src/main/java/com/owncloud/android/operations/SynchronizeFileOperation.java
  31. 3 3
      src/main/java/com/owncloud/android/operations/UploadException.java
  32. 47 91
      src/main/java/com/owncloud/android/operations/UploadFileOperation.java
  33. 9 3
      src/main/java/com/owncloud/android/providers/DocumentsStorageProvider.java
  34. 20 0
      src/main/java/com/owncloud/android/providers/FileContentProvider.java
  35. 7 6
      src/main/java/com/owncloud/android/services/OperationsService.java
  36. 2 2
      src/main/java/com/owncloud/android/services/SyncFolderHandler.java
  37. 1 1
      src/main/java/com/owncloud/android/syncadapter/FileSyncAdapter.java
  38. 9 9
      src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.java
  39. 2 3
      src/main/java/com/owncloud/android/ui/activity/ReceiveExternalFilesActivity.java
  40. 5 2
      src/main/java/com/owncloud/android/ui/adapter/OCFileListAdapter.java
  41. 0 3
      src/main/java/com/owncloud/android/ui/dialog/ChooseRichDocumentsTemplateDialogFragment.java
  42. 2 2
      src/main/java/com/owncloud/android/ui/dialog/CreateFolderDialogFragment.java
  43. 2 2
      src/main/java/com/owncloud/android/ui/dialog/SetupEncryptionDialogFragment.java
  44. 1 1
      src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.java
  45. 8 4
      src/main/java/com/owncloud/android/ui/fragment/OCFileListBottomSheetDialog.java
  46. 3 2
      src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java
  47. 2 2
      src/main/java/com/owncloud/android/ui/fragment/SearchShareesFragment.java
  48. 5 5
      src/main/java/com/owncloud/android/ui/fragment/ShareFileFragment.java
  49. 6 7
      src/main/java/com/owncloud/android/ui/helpers/FileOperationsHelper.java
  50. 0 1
      src/main/java/com/owncloud/android/ui/trashbin/TrashbinActivity.java
  51. 99 2
      src/main/java/com/owncloud/android/utils/EncryptionUtils.java
  52. 2 1
      src/main/java/com/owncloud/android/utils/FileStorageUtils.java
  53. 1 1
      src/main/java/com/owncloud/android/utils/GetShareWithUsersAsyncTask.java
  54. 2 0
      src/main/java/com/owncloud/android/utils/MimeType.java
  55. 2 2
      src/main/java/com/owncloud/android/utils/MimeTypeUtil.java

+ 2 - 0
.drone.yml

@@ -126,6 +126,8 @@ services:
       - su www-data -c "php /var/www/html/occ config:app:set circles --value 1 local_is_non_ssl"
       - su www-data -c "php /var/www/html/occ config:system:set allow_local_remote_servers --value true --type bool"
       - su www-data -c "php /var/www/html/occ circles:manage:create test public publicCircle"
+      - su www-data -c "git clone -b master https://github.com/nextcloud/end_to_end_encryption/  /var/www/html/apps/end_to_end_encryption/"
+      - su www-data -c "php /var/www/html/occ app:enable end_to_end_encryption"
       - /usr/local/bin/run.sh
 
 trigger:

+ 1 - 0
build.gradle

@@ -397,6 +397,7 @@ dependencies {
     implementation "com.github.stateless4j:stateless4j:2.6.0"
     androidTestImplementation "com.nhaarman.mockitokotlin2:mockito-kotlin:2.2.0"
     androidTestImplementation "org.mockito:mockito-android:3.3.3"
+    androidTestImplementation 'net.bytebuddy:byte-buddy:1.10.5'
 }
 
 configurations.all {

+ 1 - 1
scripts/analysis/findbugs-results.txt

@@ -1 +1 @@
-372
+372

+ 2 - 1
spotbugs-filter.xml

@@ -31,7 +31,7 @@
     <Match>
         <Or>
             <Class name="~.*BindingImpl"/>
-            <Class name="~.*\.DataBinderMapperImpl"/>	
+            <Class name="~.*\.DataBinderMapperImpl" />
         </Or>
     </Match>
 
@@ -53,6 +53,7 @@
     <Bug pattern="PATH_TRAVERSAL_IN" />
     <Bug pattern="ANDROID_EXTERNAL_FILE_ACCESS" />
     <Bug pattern="BAS_BLOATED_ASSIGNMENT_SCOPE" />
+    <Bug pattern="IMC_IMMATURE_CLASS_BAD_SERIALVERSIONUID" />
 
     <!-- This is unmanageable for now due to large amount of interconnected static state -->
     <Bug pattern="FCCD_FIND_CLASS_CIRCULAR_DEPENDENCY" />

+ 384 - 0
src/androidTest/java/com/nextcloud/client/EndToEndRandomIT.java

@@ -0,0 +1,384 @@
+/*
+ *
+ * Nextcloud Android client application
+ *
+ * @author Tobias Kaminsky
+ * Copyright (C) 2020 Tobias Kaminsky
+ * Copyright (C) 2020 Nextcloud GmbH
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package com.nextcloud.client;
+
+import android.accounts.AccountManager;
+
+import com.owncloud.android.AbstractIT;
+import com.owncloud.android.datamodel.ArbitraryDataProvider;
+import com.owncloud.android.datamodel.OCFile;
+import com.owncloud.android.db.OCUpload;
+import com.owncloud.android.lib.common.accounts.AccountUtils;
+import com.owncloud.android.lib.common.operations.RemoteOperationResult;
+import com.owncloud.android.lib.common.utils.Log_OC;
+import com.owncloud.android.lib.resources.e2ee.ToggleEncryptionRemoteOperation;
+import com.owncloud.android.lib.resources.users.GetPrivateKeyOperation;
+import com.owncloud.android.lib.resources.users.GetPublicKeyOperation;
+import com.owncloud.android.lib.resources.users.SendCSROperation;
+import com.owncloud.android.lib.resources.users.StorePrivateKeyOperation;
+import com.owncloud.android.operations.RemoveFileOperation;
+import com.owncloud.android.utils.CsrHelper;
+import com.owncloud.android.utils.EncryptionUtils;
+import com.owncloud.android.utils.FileStorageUtils;
+
+import net.bytebuddy.utility.RandomString;
+
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import java.io.File;
+import java.io.IOException;
+import java.security.KeyPair;
+import java.security.PrivateKey;
+import java.util.ArrayList;
+import java.util.Random;
+
+import static junit.framework.TestCase.assertNotNull;
+import static junit.framework.TestCase.assertTrue;
+
+public class EndToEndRandomIT extends AbstractIT {
+    public enum Action {
+        CREATE_FOLDER,
+        GO_INTO_FOLDER,
+        GO_UP,
+        UPLOAD_FILE
+    }
+
+    private static ArbitraryDataProvider arbitraryDataProvider;
+
+    private OCFile currentFolder;
+    private int actionCount = 20;
+    private String rootEncFolder = "/e/";
+
+    @BeforeClass
+    public static void initClass() {
+        arbitraryDataProvider = new ArbitraryDataProvider(targetContext.getContentResolver());
+    }
+
+    @Test
+    public void run() throws Exception {
+        init();
+
+        for (int i = 0; i < actionCount; i++) {
+            Action nextAction = Action.values()[new Random().nextInt(Action.values().length)];
+
+            switch (nextAction) {
+                case CREATE_FOLDER:
+                    createFolder(i);
+                    break;
+
+                case GO_INTO_FOLDER:
+                    goIntoFolder(i);
+                    break;
+
+                case GO_UP:
+                    goUp(i);
+                    break;
+
+                case UPLOAD_FILE:
+                    uploadFile(i);
+                    break;
+
+                default:
+                    Log_OC.d(this, "[" + i + "/" + actionCount + "]" + " Unknown action: " + nextAction);
+                    break;
+            }
+        }
+    }
+
+    @Test
+    public void uploadOneFile() throws Exception {
+        init();
+
+        uploadFile(0);
+    }
+
+    @Test
+    public void createFolder() throws Exception {
+        init();
+
+        currentFolder = createFolder(0);
+        assertNotNull(currentFolder);
+    }
+
+    @Test
+    public void createSubFolders() throws Exception {
+        init();
+
+        currentFolder = createFolder(0);
+        assertNotNull(currentFolder);
+
+        currentFolder = createFolder(1);
+        assertNotNull(currentFolder);
+
+        currentFolder = createFolder(2);
+        assertNotNull(currentFolder);
+    }
+
+    @Test
+    public void createSubFoldersWithFiles() throws Exception {
+        init();
+
+        currentFolder = createFolder(0);
+        assertNotNull(currentFolder);
+
+        uploadFile(1);
+        uploadFile(1);
+        uploadFile(2);
+
+        currentFolder = createFolder(1);
+        assertNotNull(currentFolder);
+        uploadFile(11);
+        uploadFile(12);
+        uploadFile(13);
+
+        currentFolder = createFolder(2);
+        assertNotNull(currentFolder);
+
+        uploadFile(21);
+        uploadFile(22);
+        uploadFile(23);
+    }
+
+    @Test
+    public void pseudoRandom() throws Exception {
+        init();
+
+        uploadFile(1);
+        createFolder(2);
+        goIntoFolder(3);
+        goUp(4);
+        createFolder(5);
+        uploadFile(6);
+        goUp(7);
+        goIntoFolder(8);
+        goIntoFolder(9);
+        uploadFile(10);
+    }
+
+    @Test
+    public void deleteFile() throws Exception {
+        init();
+
+        uploadFile(1);
+        deleteFile(1);
+    }
+
+    private void init() throws Exception {
+        // create folder
+        createFolder(rootEncFolder);
+        OCFile encFolder = createFolder(rootEncFolder + RandomString.make(5) + "/");
+
+        // encrypt it
+        assertTrue(new ToggleEncryptionRemoteOperation(encFolder.getLocalId(),
+                                                       encFolder.getRemotePath(),
+                                                       true)
+                       .execute(client).isSuccess());
+        encFolder.setEncrypted(true);
+        getStorageManager().saveFolder(encFolder, new ArrayList<>(), new ArrayList<>());
+
+        useExistingKeys();
+
+        rootEncFolder = encFolder.getDecryptedRemotePath();
+        currentFolder = encFolder;
+    }
+
+    private OCFile createFolder(int i) {
+        String path = currentFolder.getDecryptedRemotePath() + RandomString.make(5) + "/";
+        Log_OC.d(this, "[" + i + "/" + actionCount + "] " + "Create folder: " + path);
+
+        return createFolder(path);
+    }
+
+    private void goIntoFolder(int i) {
+        ArrayList<OCFile> folders = new ArrayList<>();
+        for (OCFile file : getStorageManager().getFolderContent(currentFolder, false)) {
+            if (file.isFolder()) {
+                folders.add(file);
+            }
+        }
+
+        if (folders.isEmpty()) {
+            Log_OC.d(this, "[" + i + "/" + actionCount + "] " + "Go into folder: No folders");
+            return;
+        }
+
+        currentFolder = folders.get(new Random().nextInt(folders.size()));
+        Log_OC.d(this,
+                 "[" + i + "/" + actionCount + "] " + "Go into folder: " + currentFolder.getDecryptedRemotePath());
+    }
+
+    private void goUp(int i) {
+        if (currentFolder.getRemotePath().equals(rootEncFolder)) {
+            Log_OC.d(this,
+                     "[" + i + "/" + actionCount + "] " + "Go up to folder: " + currentFolder.getDecryptedRemotePath());
+            return;
+        }
+
+        currentFolder = getStorageManager().getFileById(currentFolder.getParentId());
+        if (currentFolder == null) {
+            throw new RuntimeException("Current folder is null");
+        }
+
+        Log_OC.d(this,
+                 "[" + i + "/" + actionCount + "] " + "Go up to folder: " + currentFolder.getDecryptedRemotePath());
+    }
+
+    private void uploadFile(int i) throws IOException {
+        String fileName = RandomString.make(5) + ".txt";
+        createFile(fileName, new Random().nextInt(50000));
+        String path = currentFolder.getRemotePath() + fileName;
+
+        Log_OC.d(this,
+                 "[" + i + "/" + actionCount + "] " +
+                     "Upload file to: " + currentFolder.getDecryptedRemotePath() + fileName);
+
+        OCUpload ocUpload = new OCUpload(FileStorageUtils.getSavePath(account.name) + File.separator + fileName,
+                                         path,
+                                         account.name);
+        uploadOCUpload(ocUpload);
+    }
+
+    private void deleteFile(int i) {
+        ArrayList<OCFile> files = new ArrayList<>();
+        for (OCFile file : getStorageManager().getFolderContent(currentFolder, false)) {
+            if (!file.isFolder()) {
+                files.add(file);
+            }
+        }
+
+        OCFile fileToDelete = files.get(new Random().nextInt(files.size()));
+
+        Log_OC.d(this,
+                 "[" + i + "/" + actionCount + "] " +
+                     "Delete file: " + currentFolder.getDecryptedRemotePath() + fileToDelete.getDecryptedFileName());
+
+        assertTrue(new RemoveFileOperation(fileToDelete,
+                                           false,
+                                           account,
+                                           false,
+                                           targetContext)
+                       .execute(client, getStorageManager())
+                       .isSuccess());
+    }
+
+    @Test
+    public void reInit() throws Exception {
+        // create folder
+        OCFile encFolder = createFolder(rootEncFolder);
+
+        // encrypt it
+        assertTrue(new ToggleEncryptionRemoteOperation(encFolder.getLocalId(),
+                                                       encFolder.getRemotePath(),
+                                                       true)
+                       .execute(client).isSuccess());
+        encFolder.setEncrypted(true);
+        getStorageManager().saveFolder(encFolder, new ArrayList<>(), new ArrayList<>());
+
+        createKeys();
+
+        // delete keys
+        arbitraryDataProvider.deleteKeyForAccount(account.name, EncryptionUtils.PRIVATE_KEY);
+        arbitraryDataProvider.deleteKeyForAccount(account.name, EncryptionUtils.PUBLIC_KEY);
+        arbitraryDataProvider.deleteKeyForAccount(account.name, EncryptionUtils.MNEMONIC);
+
+        useExistingKeys();
+    }
+
+    private void useExistingKeys() throws Exception {
+        // download them from server
+        GetPublicKeyOperation publicKeyOperation = new GetPublicKeyOperation();
+        RemoteOperationResult publicKeyResult = publicKeyOperation.execute(account, targetContext);
+
+        assertTrue(publicKeyResult.isSuccess());
+
+        String publicKeyFromServer = (String) publicKeyResult.getData().get(0);
+        arbitraryDataProvider.storeOrUpdateKeyValue(account.name,
+                                                    EncryptionUtils.PUBLIC_KEY,
+                                                    publicKeyFromServer);
+
+        GetPrivateKeyOperation privateKeyOperation = new GetPrivateKeyOperation();
+        RemoteOperationResult privateKeyResult = privateKeyOperation.execute(account, targetContext);
+        assertTrue(privateKeyResult.isSuccess());
+
+        String privateKey = (String) privateKeyResult.getData().get(0);
+
+        String mnemonic = generateMnemonicString();
+        String decryptedPrivateKey = EncryptionUtils.decryptPrivateKey(privateKey, mnemonic);
+
+        arbitraryDataProvider.storeOrUpdateKeyValue(account.name,
+                                                    EncryptionUtils.PRIVATE_KEY, decryptedPrivateKey);
+
+        Log_OC.d(this, "Private key successfully decrypted and stored");
+
+        arbitraryDataProvider.storeOrUpdateKeyValue(account.name, EncryptionUtils.MNEMONIC, mnemonic);
+    }
+
+    /*
+    TODO do not c&p code
+     */
+    private void createKeys() throws Exception {
+        String publicKey;
+
+        // Create public/private key pair
+        KeyPair keyPair = EncryptionUtils.generateKeyPair();
+
+        // create CSR
+        AccountManager accountManager = AccountManager.get(targetContext);
+        String userId = accountManager.getUserData(account, AccountUtils.Constants.KEY_USER_ID);
+        String urlEncoded = CsrHelper.generateCsrPemEncodedString(keyPair, userId);
+
+        SendCSROperation operation = new SendCSROperation(urlEncoded);
+        RemoteOperationResult result = operation.execute(account, targetContext);
+
+        if (result.isSuccess()) {
+            publicKey = (String) result.getData().get(0);
+        } else {
+            throw new Exception("failed to send CSR", result.getException());
+        }
+
+        PrivateKey privateKey = keyPair.getPrivate();
+        String privateKeyString = EncryptionUtils.encodeBytesToBase64String(privateKey.getEncoded());
+        String privatePemKeyString = EncryptionUtils.privateKeyToPEM(privateKey);
+        String encryptedPrivateKey = EncryptionUtils.encryptPrivateKey(privatePemKeyString,
+                                                                       generateMnemonicString());
+
+        // upload encryptedPrivateKey
+        StorePrivateKeyOperation storePrivateKeyOperation = new StorePrivateKeyOperation(encryptedPrivateKey);
+        RemoteOperationResult storePrivateKeyResult = storePrivateKeyOperation.execute(account, targetContext);
+
+        if (storePrivateKeyResult.isSuccess()) {
+            arbitraryDataProvider.storeOrUpdateKeyValue(account.name, EncryptionUtils.PRIVATE_KEY,
+                                                        privateKeyString);
+            arbitraryDataProvider.storeOrUpdateKeyValue(account.name, EncryptionUtils.PUBLIC_KEY, publicKey);
+            arbitraryDataProvider.storeOrUpdateKeyValue(account.name, EncryptionUtils.MNEMONIC,
+                                                        generateMnemonicString());
+        } else {
+            throw new RuntimeException("Error uploading private key!");
+        }
+    }
+
+    private String generateMnemonicString() {
+        return "1 2 3 4 5 6";
+    }
+}

+ 3 - 2
src/androidTest/java/com/nextcloud/client/FileDisplayActivityIT.java

@@ -163,11 +163,12 @@ public class FileDisplayActivityIT extends AbstractIT {
 
     @Test
     public void allFiles() {
-        // ActivityScenario<FileDisplayActivity> sut = ActivityScenario.launch(FileDisplayActivity.class);
         FileDisplayActivity sut = activityRule.launchActivity(null);
 
         // given test folder
-        assertTrue(new CreateFolderOperation("/test/", true).execute(client, getStorageManager()).isSuccess());
+        assertTrue(new CreateFolderOperation("/test/", account, targetContext)
+                       .execute(client, getStorageManager())
+                       .isSuccess());
 
         // navigate into it
         OCFile test = getStorageManager().getFileByPath("/test/");

+ 96 - 6
src/androidTest/java/com/owncloud/android/AbstractIT.java

@@ -10,27 +10,37 @@ import android.content.Context;
 import android.net.Uri;
 import android.os.Bundle;
 
+import com.evernote.android.job.JobRequest;
 import com.facebook.testing.screenshot.Screenshot;
-import com.nextcloud.client.RetryTestRule;
 import com.nextcloud.client.account.UserAccountManager;
 import com.nextcloud.client.account.UserAccountManagerImpl;
+import com.nextcloud.client.device.PowerManagementService;
+import com.nextcloud.client.network.ConnectivityService;
 import com.owncloud.android.datamodel.FileDataStorageManager;
+import com.owncloud.android.datamodel.OCFile;
+import com.owncloud.android.datamodel.UploadsStorageManager;
+import com.owncloud.android.db.OCUpload;
+import com.owncloud.android.files.services.FileUploader;
 import com.owncloud.android.lib.common.OwnCloudClient;
 import com.owncloud.android.lib.common.OwnCloudClientFactory;
 import com.owncloud.android.lib.common.accounts.AccountUtils;
 import com.owncloud.android.lib.common.operations.RemoteOperationResult;
+import com.owncloud.android.lib.resources.e2ee.ToggleEncryptionRemoteOperation;
 import com.owncloud.android.lib.resources.files.ReadFolderRemoteOperation;
 import com.owncloud.android.lib.resources.files.RemoveFileRemoteOperation;
 import com.owncloud.android.lib.resources.files.model.RemoteFile;
+import com.owncloud.android.operations.CreateFolderOperation;
+import com.owncloud.android.operations.UploadFileOperation;
 import com.owncloud.android.utils.FileStorageUtils;
 
+import junit.framework.TestCase;
+
 import org.apache.commons.httpclient.HttpStatus;
 import org.apache.commons.httpclient.methods.GetMethod;
 import org.apache.commons.io.FileUtils;
 import org.junit.After;
 import org.junit.Assert;
 import org.junit.BeforeClass;
-import org.junit.Rule;
 
 import java.io.File;
 import java.io.FileWriter;
@@ -56,7 +66,7 @@ import static org.junit.Assert.assertTrue;
 
 //@RunWith(AndroidJUnit4.class)
 public abstract class AbstractIT {
-    @Rule public RetryTestRule retryTestRule = new RetryTestRule();
+    // @Rule public RetryTestRule retryTestRule = new RetryTestRule();
 
     protected static OwnCloudClient client;
     protected static Account account;
@@ -99,7 +109,7 @@ public abstract class AbstractIT {
 
             waitForServer(client, baseUrl);
 
-            deleteAllFiles(); // makes sure that no file/folder is in root
+//            deleteAllFiles(); // makes sure that no file/folder is in root
         } catch (OperationCanceledException e) {
             e.printStackTrace();
         } catch (AuthenticatorException e) {
@@ -124,8 +134,18 @@ public abstract class AbstractIT {
             RemoteFile remoteFile = (RemoteFile) object;
 
             if (!remoteFile.getRemotePath().equals("/")) {
+                if (remoteFile.isEncrypted()) {
+                    assertTrue(new ToggleEncryptionRemoteOperation(remoteFile.getLocalId(),
+                                                                   remoteFile.getRemotePath(),
+                                                                   false)
+                                   .execute(client)
+                                   .isSuccess());
+                }
+
                 assertTrue(new RemoveFileRemoteOperation(remoteFile.getRemotePath())
-                               .execute(client).isSuccess());
+                               .execute(client)
+                               .isSuccess()
+                          );
             }
         }
     }
@@ -147,7 +167,7 @@ public abstract class AbstractIT {
         createFile("chunkedFile.txt", 500000);
     }
 
-    private static void createFile(String name, int iteration) throws IOException {
+    public static void createFile(String name, int iteration) throws IOException {
         File file = new File(FileStorageUtils.getSavePath(account.name) + File.separator + name);
         file.createNewFile();
 
@@ -235,4 +255,74 @@ public abstract class AbstractIT {
             e.printStackTrace();
         }
     }
+
+    public OCFile createFolder(String remotePath) {
+        TestCase.assertTrue(new CreateFolderOperation(remotePath, account, targetContext)
+                                .execute(client, getStorageManager())
+                                .isSuccess());
+
+        return getStorageManager().getFileByDecryptedRemotePath(remotePath);
+    }
+
+    public void uploadOCUpload(OCUpload ocUpload) {
+        ConnectivityService connectivityServiceMock = new ConnectivityService() {
+            @Override
+            public boolean isInternetWalled() {
+                return false;
+            }
+
+            @Override
+            public boolean isOnlineWithWifi() {
+                return true;
+            }
+
+            @Override
+            public JobRequest.NetworkType getActiveNetworkType() {
+                return JobRequest.NetworkType.ANY;
+            }
+        };
+
+        PowerManagementService powerManagementServiceMock = new PowerManagementService() {
+            @Override
+            public boolean isPowerSavingEnabled() {
+                return false;
+            }
+
+            @Override
+            public boolean isPowerSavingExclusionAvailable() {
+                return false;
+            }
+
+            @Override
+            public boolean isBatteryCharging() {
+                return false;
+            }
+        };
+
+        UserAccountManager accountManager = UserAccountManagerImpl.fromContext(targetContext);
+        UploadsStorageManager uploadsStorageManager = new UploadsStorageManager(accountManager,
+                                                                                targetContext.getContentResolver());
+
+        UploadFileOperation newUpload = new UploadFileOperation(
+            uploadsStorageManager,
+            connectivityServiceMock,
+            powerManagementServiceMock,
+            account,
+            null,
+            ocUpload,
+            FileUploader.NameCollisionPolicy.DEFAULT,
+            FileUploader.LOCAL_BEHAVIOUR_COPY,
+            targetContext,
+            false,
+            false
+        );
+        newUpload.addRenameUploadListener(() -> {
+            // dummy
+        });
+
+        newUpload.setRemoteFolderToBeCreated();
+
+        RemoteOperationResult result = newUpload.execute(client, getStorageManager());
+        assertTrue(result.getLogMessage(), result.isSuccess());
+    }
 }

+ 14 - 6
src/androidTest/java/com/owncloud/android/FileIT.java

@@ -1,5 +1,6 @@
 package com.owncloud.android;
 
+import com.owncloud.android.datamodel.OCFile;
 import com.owncloud.android.lib.common.operations.RemoteOperationResult;
 import com.owncloud.android.operations.CreateFolderOperation;
 import com.owncloud.android.operations.RemoveFileOperation;
@@ -26,16 +27,17 @@ public class FileIT extends AbstractIT {
         // folder does not exist yet
         assertNull(getStorageManager().getFileByPath(path));
 
-        SyncOperation syncOp = new CreateFolderOperation(path, true);
+        SyncOperation syncOp = new CreateFolderOperation(path, account, targetContext);
         RemoteOperationResult result = syncOp.execute(client, getStorageManager());
 
         assertTrue(result.toString(), result.isSuccess());
 
         // folder exists
-        assertTrue(getStorageManager().getFileByPath(path).isFolder());
+        OCFile file = getStorageManager().getFileByPath(path);
+        assertTrue(file.isFolder());
 
         // cleanup
-        new RemoveFileOperation(path, false, account, false, targetContext).execute(client, getStorageManager());
+        new RemoveFileOperation(file, false, account, false, targetContext).execute(client, getStorageManager());
     }
 
     @Test
@@ -44,14 +46,20 @@ public class FileIT extends AbstractIT {
         // folder does not exist yet
         assertNull(getStorageManager().getFileByPath(path));
 
-        SyncOperation syncOp = new CreateFolderOperation(path, true);
+        SyncOperation syncOp = new CreateFolderOperation(path, account, targetContext);
         RemoteOperationResult result = syncOp.execute(client, getStorageManager());
         assertTrue(result.toString(), result.isSuccess());
 
         // folder exists
-        assertTrue(getStorageManager().getFileByPath(path).isFolder());
+        OCFile file = getStorageManager().getFileByPath(path);
+        assertTrue(file.isFolder());
 
         // cleanup
-        new RemoveFileOperation("/testFolder/", false, account, false, targetContext).execute(client, getStorageManager());
+        new RemoveFileOperation(file,
+                                false,
+                                account,
+                                false,
+                                targetContext)
+            .execute(client, getStorageManager());
     }
 }

+ 1 - 1
src/androidTest/java/com/owncloud/android/ScreenshotsIT.java

@@ -86,7 +86,7 @@ public class ScreenshotsIT extends AbstractIT {
 
         // folder does not exist yet
         if (getStorageManager().getFileByPath(path) == null) {
-            SyncOperation syncOp = new CreateFolderOperation(path, true);
+            SyncOperation syncOp = new CreateFolderOperation(path, account, targetContext);
             RemoteOperationResult result = syncOp.execute(client, getStorageManager());
 
             assertTrue(result.isSuccess());

+ 36 - 66
src/androidTest/java/com/owncloud/android/UploadIT.java

@@ -30,11 +30,9 @@ import com.nextcloud.client.device.PowerManagementService;
 import com.nextcloud.client.network.Connectivity;
 import com.nextcloud.client.network.ConnectivityService;
 import com.owncloud.android.datamodel.UploadsStorageManager;
+import com.owncloud.android.datamodel.OCFile;
 import com.owncloud.android.db.OCUpload;
-import com.owncloud.android.files.services.FileUploader;
-import com.owncloud.android.lib.common.operations.RemoteOperationResult;
 import com.owncloud.android.operations.RemoveFileOperation;
-import com.owncloud.android.operations.UploadFileOperation;
 import com.owncloud.android.utils.FileStorageUtils;
 
 import org.jetbrains.annotations.NotNull;
@@ -44,8 +42,6 @@ import org.junit.runner.RunWith;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 
-import static junit.framework.TestCase.assertTrue;
-
 /**
  * Tests related to file uploads
  */
@@ -95,92 +91,66 @@ public class UploadIT extends AbstractIT {
     @Test
     public void testEmptyUpload() {
         OCUpload ocUpload = new OCUpload(FileStorageUtils.getSavePath(account.name) + "/empty.txt",
-            "/testUpload/empty.txt", account.name);
-
-        RemoteOperationResult result = testUpload(ocUpload);
+                                         "/testUpload/empty.txt",
+                                         account.name);
 
-        assertTrue(result.toString(), result.isSuccess());
+        uploadOCUpload(ocUpload);
 
         // cleanup
-        new RemoveFileOperation("/testUpload/", false, account, false, targetContext).execute(client, getStorageManager());
+        new RemoveFileOperation(new OCFile("/testUpload/"),
+                                false,
+                                account,
+                                false,
+                                targetContext)
+            .execute(client, getStorageManager());
     }
 
     @Test
     public void testNonEmptyUpload() {
         OCUpload ocUpload = new OCUpload(FileStorageUtils.getSavePath(account.name) + "/nonEmpty.txt",
-            "/testUpload/nonEmpty.txt", account.name);
+                                         "/testUpload/nonEmpty.txt",
+                                         account.name);
 
-        RemoteOperationResult result = testUpload(ocUpload);
-
-        assertTrue(result.toString(), result.isSuccess());
+        uploadOCUpload(ocUpload);
 
         // cleanup
-        new RemoveFileOperation("/testUpload/", false, account, false, targetContext).execute(client, getStorageManager());
+        new RemoveFileOperation(new OCFile("/testUpload/"),
+                                false,
+                                account,
+                                false,
+                                targetContext)
+            .execute(client, getStorageManager());
     }
 
     @Test
     public void testChunkedUpload() {
         OCUpload ocUpload = new OCUpload(FileStorageUtils.getSavePath(account.name) + "/chunkedFile.txt",
-            "/testUpload/chunkedFile.txt", account.name);
-
-        RemoteOperationResult result = testUpload(ocUpload);
+                                         "/testUpload/chunkedFile.txt", account.name);
 
-        assertTrue(result.toString(), result.isSuccess());
+        uploadOCUpload(ocUpload);
 
         // cleanup
-        new RemoveFileOperation("/testUpload/", false, account, false, targetContext).execute(client, getStorageManager());
-    }
-
-    public RemoteOperationResult testUpload(OCUpload ocUpload) {
-        UploadFileOperation newUpload = new UploadFileOperation(
-            storageManager,
-            connectivityServiceMock,
-            powerManagementServiceMock,
-            account,
-            null,
-            ocUpload,
-            FileUploader.NameCollisionPolicy.DEFAULT,
-            FileUploader.LOCAL_BEHAVIOUR_COPY,
-            targetContext,
-            false,
-            false
-        );
-        newUpload.addRenameUploadListener(() -> {
-            // dummy
-        });
-
-        newUpload.setRemoteFolderToBeCreated();
-
-        return newUpload.execute(client, getStorageManager());
+        new RemoveFileOperation(new OCFile("/testUpload/"),
+                                false,
+                                account,
+                                false,
+                                targetContext)
+            .execute(client, getStorageManager());
     }
 
     @Test
     public void testUploadInNonExistingFolder() {
         OCUpload ocUpload = new OCUpload(FileStorageUtils.getSavePath(account.name) + "/empty.txt",
-                "/testUpload/2/3/4/1.txt", account.name);
-        UploadFileOperation newUpload = new UploadFileOperation(
-            storageManager,
-            connectivityServiceMock,
-            powerManagementServiceMock,
-            account,
-            null,
-            ocUpload,
-            FileUploader.NameCollisionPolicy.DEFAULT,
-            FileUploader.LOCAL_BEHAVIOUR_COPY,
-            targetContext,
-            false,
-            false
-        );
-        newUpload.addRenameUploadListener(() -> {
-            // dummy
-        });
-
-        newUpload.setRemoteFolderToBeCreated();
-
-        RemoteOperationResult result = newUpload.execute(client, getStorageManager());
-        assertTrue(result.toString(), result.isSuccess());
+                                         "/testUpload/2/3/4/1.txt", account.name);
+
+        uploadOCUpload(ocUpload);
 
         // cleanup
-        new RemoveFileOperation("/testUpload/", false, account, false, targetContext).execute(client, getStorageManager());
+        new RemoveFileOperation(new OCFile("/testUpload/"),
+                                false,
+                                account,
+                                false,
+                                targetContext)
+            .execute(client, getStorageManager());
     }
 }

+ 4 - 4
src/androidTest/java/com/owncloud/android/ui/activity/FolderPickerActivityIT.java

@@ -44,7 +44,7 @@ public class FolderPickerActivityIT {
         // Arrange
         FolderPickerActivity targetActivity = activityRule.getActivity();
         OCFile origin = new OCFile("/test/file.test");
-        origin.setRemotePath("/remotePath/test");
+        origin.setEncryptedRemotePath("/remotePath/test");
 
         // Act
         targetActivity.setFile(origin);
@@ -60,7 +60,7 @@ public class FolderPickerActivityIT {
         FolderPickerActivity targetActivity = activityRule.getActivity();
         OCFile origin = new OCFile("/test/");
         origin.setFileId(1);
-        origin.setRemotePath("/test/");
+        origin.setEncryptedRemotePath("/test/");
         origin.setStoragePath("/test/");
         origin.setFolder();
 
@@ -78,7 +78,7 @@ public class FolderPickerActivityIT {
         FolderPickerActivity targetActivity = activityRule.getActivity();
         OCFile origin = new OCFile("/");
         origin.setFileId(1);
-        origin.setRemotePath("/");
+        origin.setEncryptedRemotePath("/");
         origin.setStoragePath("/");
         origin.setFolder();
 
@@ -109,7 +109,7 @@ public class FolderPickerActivityIT {
         // Arrange
         FolderPickerActivity targetActivity = activityRule.getActivity();
         OCFile origin = new OCFile("/test/file.test");
-        origin.setRemotePath("/test/file.test");
+        origin.setEncryptedRemotePath("/test/file.test");
 
         OCFile target = new OCFile("/test/");
 

+ 13 - 5
src/androidTest/java/com/owncloud/android/ui/fragment/OCFileListFragmentIT.kt

@@ -91,7 +91,7 @@ class OCFileListFragmentIT : AbstractIT() {
 
     @Test
     fun showRichWorkspace() {
-        assertTrue(CreateFolderOperation("/test/", true).execute(client, storageManager).isSuccess)
+        assertTrue(CreateFolderOperation("/test/", account, targetContext).execute(client, storageManager).isSuccess)
 
         val ocUpload = OCUpload(FileStorageUtils.getSavePath(account.name) + "/nonEmpty.txt",
             "/test/Readme.md",
@@ -159,7 +159,9 @@ class OCFileListFragmentIT : AbstractIT() {
     @Test
     fun createAndShowShareToUser() {
         val path = "/shareToAdmin/"
-        TestCase.assertTrue(CreateFolderOperation(path, true).execute(client, storageManager).isSuccess)
+        TestCase.assertTrue(CreateFolderOperation(path, account, targetContext)
+            .execute(client, storageManager)
+            .isSuccess)
 
         // share folder to user "admin"
         TestCase.assertTrue(CreateShareRemoteOperation(path,
@@ -181,7 +183,9 @@ class OCFileListFragmentIT : AbstractIT() {
     @Test
     fun createAndShowShareToGroup() {
         val path = "/shareToGroup/"
-        TestCase.assertTrue(CreateFolderOperation(path, true).execute(client, storageManager).isSuccess)
+        TestCase.assertTrue(CreateFolderOperation(path, account, targetContext)
+            .execute(client, storageManager)
+            .isSuccess)
 
         // share folder to group
         assertTrue(CreateShareRemoteOperation("/shareToGroup/",
@@ -203,7 +207,9 @@ class OCFileListFragmentIT : AbstractIT() {
     @Test
     fun createAndShowShareToCircle() {
         val path = "/shareToCircle/"
-        TestCase.assertTrue(CreateFolderOperation(path, true).execute(client, storageManager).isSuccess)
+        TestCase.assertTrue(CreateFolderOperation(path, account, targetContext)
+            .execute(client, storageManager)
+            .isSuccess)
 
         // share folder to circle
         // get circleId
@@ -232,7 +238,9 @@ class OCFileListFragmentIT : AbstractIT() {
     @Test
     fun createAndShowShareViaLink() {
         val path = "/shareViaLink/"
-        TestCase.assertTrue(CreateFolderOperation(path, true).execute(client, storageManager).isSuccess)
+        TestCase.assertTrue(CreateFolderOperation(path, account, targetContext)
+            .execute(client, storageManager)
+            .isSuccess)
 
         // share folder via public link
         TestCase.assertTrue(CreateShareRemoteOperation("/shareViaLink/",

+ 183 - 45
src/androidTest/java/com/owncloud/android/util/EncryptionTestIT.java

@@ -22,15 +22,19 @@
 package com.owncloud.android.util;
 
 import android.os.Build;
+import android.text.TextUtils;
 
 import com.google.gson.JsonElement;
 import com.google.gson.JsonParser;
 import com.google.gson.reflect.TypeToken;
 import com.owncloud.android.datamodel.DecryptedFolderMetadata;
 import com.owncloud.android.datamodel.EncryptedFolderMetadata;
+import com.owncloud.android.lib.common.utils.Log_OC;
 import com.owncloud.android.utils.CsrHelper;
 import com.owncloud.android.utils.EncryptionUtils;
 
+import net.bytebuddy.utility.RandomString;
+
 import org.apache.commons.io.FileUtils;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -52,6 +56,26 @@ import androidx.annotation.RequiresApi;
 import androidx.test.runner.AndroidJUnit4;
 
 import static androidx.test.InstrumentationRegistry.getInstrumentation;
+import static com.owncloud.android.utils.EncryptionUtils.EncryptedFile;
+import static com.owncloud.android.utils.EncryptionUtils.decodeStringToBase64Bytes;
+import static com.owncloud.android.utils.EncryptionUtils.decryptFile;
+import static com.owncloud.android.utils.EncryptionUtils.decryptFolderMetaData;
+import static com.owncloud.android.utils.EncryptionUtils.decryptPrivateKey;
+import static com.owncloud.android.utils.EncryptionUtils.decryptStringAsymmetric;
+import static com.owncloud.android.utils.EncryptionUtils.decryptStringSymmetric;
+import static com.owncloud.android.utils.EncryptionUtils.deserializeJSON;
+import static com.owncloud.android.utils.EncryptionUtils.encodeBytesToBase64String;
+import static com.owncloud.android.utils.EncryptionUtils.encryptFile;
+import static com.owncloud.android.utils.EncryptionUtils.encryptFolderMetadata;
+import static com.owncloud.android.utils.EncryptionUtils.generateKey;
+import static com.owncloud.android.utils.EncryptionUtils.generateSHA512;
+import static com.owncloud.android.utils.EncryptionUtils.getMD5Sum;
+import static com.owncloud.android.utils.EncryptionUtils.ivDelimiter;
+import static com.owncloud.android.utils.EncryptionUtils.ivLength;
+import static com.owncloud.android.utils.EncryptionUtils.randomBytes;
+import static com.owncloud.android.utils.EncryptionUtils.saltLength;
+import static com.owncloud.android.utils.EncryptionUtils.serializeJSON;
+import static com.owncloud.android.utils.EncryptionUtils.verifySHA512;
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertTrue;
 import static org.junit.Assert.assertEquals;
@@ -110,25 +134,60 @@ public class EncryptionTestIT {
 
     @Test
     public void encryptStringAsymmetric() throws Exception {
-        byte[] key1 = EncryptionUtils.generateKey();
-        String base64encodedKey = EncryptionUtils.encodeBytesToBase64String(key1);
+        byte[] key1 = generateKey();
+        String base64encodedKey = encodeBytesToBase64String(key1);
 
         String encryptedString = EncryptionUtils.encryptStringAsymmetric(base64encodedKey, cert);
-        String decryptedString = EncryptionUtils.decryptStringAsymmetric(encryptedString, privateKey);
+        String decryptedString = decryptStringAsymmetric(encryptedString, privateKey);
 
-        byte[] key2 = EncryptionUtils.decodeStringToBase64Bytes(decryptedString);
+        byte[] key2 = decodeStringToBase64Bytes(decryptedString);
 
         assertTrue(Arrays.equals(key1, key2));
     }
 
+    @Test
+    public void encryptStringSymmetricRandom() throws Exception {
+        int max = 500;
+        for (int i = 0; i < max; i++) {
+            Log_OC.d("EncryptionTestIT", i + " of " + max);
+            byte[] key = generateKey();
+
+            String encryptedString = EncryptionUtils.encryptStringSymmetric(privateKey, key);
+            String decryptedString = decryptStringSymmetric(encryptedString, key);
+
+            assertEquals(privateKey, decryptedString);
+        }
+    }
+
     @Test
     public void encryptStringSymmetric() throws Exception {
-        byte[] key = EncryptionUtils.generateKey();
+        int max = 5000;
+        byte[] key = generateKey();
+
+        for (int i = 0; i < max; i++) {
+            Log_OC.d("EncryptionTestIT", i + " of " + max);
+
+            String encryptedString = EncryptionUtils.encryptStringSymmetric(privateKey, key);
+
+            int delimiterPosition = encryptedString.indexOf(ivDelimiter);
+            if (delimiterPosition == -1) {
+                throw new RuntimeException("IV not found!");
+            }
 
-        String encryptedString = EncryptionUtils.encryptStringSymmetric(privateKey, key);
-        String decryptedString = EncryptionUtils.decryptStringSymmetric(encryptedString, key);
+            String ivString = encryptedString.substring(delimiterPosition + ivDelimiter.length());
+            if (TextUtils.isEmpty(ivString)) {
+                delimiterPosition = encryptedString.lastIndexOf(ivDelimiter);
+                ivString = encryptedString.substring(delimiterPosition + ivDelimiter.length());
 
-        assertEquals(privateKey, decryptedString);
+                if (TextUtils.isEmpty(ivString)) {
+                    throw new RuntimeException("IV string is empty");
+                }
+            }
+
+            String decryptedString = decryptStringSymmetric(encryptedString, key);
+
+            assertEquals(privateKey, decryptedString);
+        }
     }
 
     @Test
@@ -140,10 +199,10 @@ public class EncryptionTestIT {
         KeyPair keyPair = keyGen.generateKeyPair();
         PrivateKey privateKey = keyPair.getPrivate();
         byte[] privateKeyBytes = privateKey.getEncoded();
-        String privateKeyString = EncryptionUtils.encodeBytesToBase64String(privateKeyBytes);
+        String privateKeyString = encodeBytesToBase64String(privateKeyBytes);
 
         String encryptedString = EncryptionUtils.encryptPrivateKey(privateKeyString, keyPhrase);
-        String decryptedString = EncryptionUtils.decryptPrivateKey(encryptedString, keyPhrase);
+        String decryptedString = decryptPrivateKey(encryptedString, keyPhrase);
 
         assertEquals(privateKeyString, decryptedString);
     }
@@ -155,7 +214,7 @@ public class EncryptionTestIT {
         KeyPair keyPair = keyGen.generateKeyPair();
 
         assertFalse(CsrHelper.generateCsrPemEncodedString(keyPair, "").isEmpty());
-        assertFalse(EncryptionUtils.encodeBytesToBase64String(keyPair.getPublic().getEncoded()).isEmpty());
+        assertFalse(encodeBytesToBase64String(keyPair.getPublic().getEncoded()).isEmpty());
     }
 
     /**
@@ -167,31 +226,31 @@ public class EncryptionTestIT {
         DecryptedFolderMetadata decryptedFolderMetadata1 = generateFolderMetadata();
 
         // encrypt
-        EncryptedFolderMetadata encryptedFolderMetadata1 = EncryptionUtils.encryptFolderMetadata(
+        EncryptedFolderMetadata encryptedFolderMetadata1 = encryptFolderMetadata(
                 decryptedFolderMetadata1, privateKey);
 
         // serialize
-        String encryptedJson = EncryptionUtils.serializeJSON(encryptedFolderMetadata1);
+        String encryptedJson = serializeJSON(encryptedFolderMetadata1);
 
         // de-serialize
-        EncryptedFolderMetadata encryptedFolderMetadata2 = EncryptionUtils.deserializeJSON(encryptedJson,
-                new TypeToken<EncryptedFolderMetadata>() {
+        EncryptedFolderMetadata encryptedFolderMetadata2 = deserializeJSON(encryptedJson,
+                                                                           new TypeToken<EncryptedFolderMetadata>() {
                 });
 
         // decrypt
-        DecryptedFolderMetadata decryptedFolderMetadata2 = EncryptionUtils.decryptFolderMetaData(
+        DecryptedFolderMetadata decryptedFolderMetadata2 = decryptFolderMetaData(
                 encryptedFolderMetadata2, privateKey);
 
         // compare
-        assertTrue(compareJsonStrings(EncryptionUtils.serializeJSON(decryptedFolderMetadata1),
-                EncryptionUtils.serializeJSON(decryptedFolderMetadata2)));
+        assertTrue(compareJsonStrings(serializeJSON(decryptedFolderMetadata1),
+                                      serializeJSON(decryptedFolderMetadata2)));
     }
 
     @Test
     public void testCryptFileWithoutMetadata() throws Exception {
-        byte[] key = EncryptionUtils.decodeStringToBase64Bytes("WANM0gRv+DhaexIsI0T3Lg==");
-        byte[] iv = EncryptionUtils.decodeStringToBase64Bytes("gKm3n+mJzeY26q4OfuZEqg==");
-        byte[] authTag = EncryptionUtils.decodeStringToBase64Bytes("PboI9tqHHX3QeAA22PIu4w==");
+        byte[] key = decodeStringToBase64Bytes("WANM0gRv+DhaexIsI0T3Lg==");
+        byte[] iv = decodeStringToBase64Bytes("gKm3n+mJzeY26q4OfuZEqg==");
+        byte[] authTag = decodeStringToBase64Bytes("PboI9tqHHX3QeAA22PIu4w==");
 
         assertTrue(cryptFile("ia7OEEEyXMoRa1QWQk8r", "78f42172166f9dc8fd1a7156b1753353", key, iv, authTag));
     }
@@ -202,25 +261,104 @@ public class EncryptionTestIT {
 
         // n9WXAIXO2wRY4R8nXwmo
         assertTrue(cryptFile("ia7OEEEyXMoRa1QWQk8r",
-                "78f42172166f9dc8fd1a7156b1753353",
-                EncryptionUtils.decodeStringToBase64Bytes(metadata.getFiles().get("ia7OEEEyXMoRa1QWQk8r")
+                             "78f42172166f9dc8fd1a7156b1753353",
+                             decodeStringToBase64Bytes(metadata.getFiles().get("ia7OEEEyXMoRa1QWQk8r")
                         .getEncrypted().getKey()),
-                EncryptionUtils.decodeStringToBase64Bytes(metadata.getFiles().get("ia7OEEEyXMoRa1QWQk8r")
+                             decodeStringToBase64Bytes(metadata.getFiles().get("ia7OEEEyXMoRa1QWQk8r")
                         .getInitializationVector()),
-                EncryptionUtils.decodeStringToBase64Bytes(metadata.getFiles().get("ia7OEEEyXMoRa1QWQk8r")
+                             decodeStringToBase64Bytes(metadata.getFiles().get("ia7OEEEyXMoRa1QWQk8r")
                         .getAuthenticationTag())));
 
         // n9WXAIXO2wRY4R8nXwmo
         assertTrue(cryptFile("n9WXAIXO2wRY4R8nXwmo",
-                "825143ed1f21ebb0c3b3c3f005b2f5db",
-                EncryptionUtils.decodeStringToBase64Bytes(metadata.getFiles().get("n9WXAIXO2wRY4R8nXwmo")
+                             "825143ed1f21ebb0c3b3c3f005b2f5db",
+                             decodeStringToBase64Bytes(metadata.getFiles().get("n9WXAIXO2wRY4R8nXwmo")
                         .getEncrypted().getKey()),
-                EncryptionUtils.decodeStringToBase64Bytes(metadata.getFiles().get("n9WXAIXO2wRY4R8nXwmo")
+                             decodeStringToBase64Bytes(metadata.getFiles().get("n9WXAIXO2wRY4R8nXwmo")
                         .getInitializationVector()),
-                EncryptionUtils.decodeStringToBase64Bytes(metadata.getFiles().get("n9WXAIXO2wRY4R8nXwmo")
+                             decodeStringToBase64Bytes(metadata.getFiles().get("n9WXAIXO2wRY4R8nXwmo")
                         .getAuthenticationTag())));
     }
 
+    @Test
+    public void bigMetadata() throws Exception {
+        DecryptedFolderMetadata decryptedFolderMetadata1 = generateFolderMetadata();
+
+        // encrypt
+        EncryptedFolderMetadata encryptedFolderMetadata1 = encryptFolderMetadata(
+            decryptedFolderMetadata1, privateKey);
+
+        // serialize
+        String encryptedJson = serializeJSON(encryptedFolderMetadata1);
+
+        // de-serialize
+        EncryptedFolderMetadata encryptedFolderMetadata2 = deserializeJSON(encryptedJson,
+                                                                           new TypeToken<EncryptedFolderMetadata>() {
+                                                                           });
+
+        // decrypt
+        DecryptedFolderMetadata decryptedFolderMetadata2 = decryptFolderMetaData(
+            encryptedFolderMetadata2, privateKey);
+
+        // compare
+        assertTrue(compareJsonStrings(serializeJSON(decryptedFolderMetadata1),
+                                      serializeJSON(decryptedFolderMetadata2)));
+
+        // prefill with 500
+        for (int i = 0; i < 500; i++) {
+            addFile(decryptedFolderMetadata1, i);
+        }
+
+        int max = 505;
+        for (int i = 500; i < max; i++) {
+            Log_OC.d(this, "Big metadata: " + i + " of " + max);
+
+            addFile(decryptedFolderMetadata1, i);
+
+            // encrypt
+            encryptedFolderMetadata1 = encryptFolderMetadata(decryptedFolderMetadata1, privateKey);
+
+            // serialize
+            encryptedJson = serializeJSON(encryptedFolderMetadata1);
+
+            // de-serialize
+            encryptedFolderMetadata2 = deserializeJSON(encryptedJson,
+                                                       new TypeToken<EncryptedFolderMetadata>() {
+                                                       });
+
+            // decrypt
+            decryptedFolderMetadata2 = decryptFolderMetaData(encryptedFolderMetadata2, privateKey);
+
+            // compare
+            assertTrue(compareJsonStrings(serializeJSON(decryptedFolderMetadata1),
+                                          serializeJSON(decryptedFolderMetadata2)));
+
+            assertEquals(i + 3, decryptedFolderMetadata1.getFiles().size());
+            assertEquals(i + 3, decryptedFolderMetadata2.getFiles().size());
+        }
+    }
+
+    private void addFile(DecryptedFolderMetadata decryptedFolderMetadata, int counter) {
+        // Add new file
+        // Always generate new
+        byte[] key = generateKey();
+        byte[] iv = randomBytes(ivLength);
+        byte[] authTag = randomBytes((128 / 8));
+
+        DecryptedFolderMetadata.Data data = new DecryptedFolderMetadata.Data();
+        data.setKey(EncryptionUtils.encodeBytesToBase64String(key));
+        data.setFilename(counter + ".txt");
+        data.setVersion(1);
+
+        DecryptedFolderMetadata.DecryptedFile file = new DecryptedFolderMetadata.DecryptedFile();
+        file.setInitializationVector(EncryptionUtils.encodeBytesToBase64String(iv));
+        file.setEncrypted(data);
+        file.setMetadataKey(0);
+        file.setAuthenticationTag(EncryptionUtils.encodeBytesToBase64String(authTag));
+
+        decryptedFolderMetadata.getFiles().put(RandomString.make(20), file);
+    }
+
     /**
      * generates new keys and tests if they are unique
      */
@@ -229,7 +367,7 @@ public class EncryptionTestIT {
         Set<String> keys = new HashSet<>();
 
         for (int i = 0; i < 50; i++) {
-            assertTrue(keys.add(EncryptionUtils.encodeBytesToBase64String(EncryptionUtils.generateKey())));
+            assertTrue(keys.add(encodeBytesToBase64String(generateKey())));
         }
     }
 
@@ -241,8 +379,8 @@ public class EncryptionTestIT {
         Set<String> ivs = new HashSet<>();
 
         for (int i = 0; i < 50; i++) {
-            assertTrue(ivs.add(EncryptionUtils.encodeBytesToBase64String(
-                    EncryptionUtils.randomBytes(EncryptionUtils.ivLength))));
+            assertTrue(ivs.add(encodeBytesToBase64String(
+                randomBytes(ivLength))));
         }
     }
 
@@ -254,8 +392,8 @@ public class EncryptionTestIT {
         Set<String> ivs = new HashSet<>();
 
         for (int i = 0; i < 50; i++) {
-            assertTrue(ivs.add(EncryptionUtils.encodeBytesToBase64String(
-                    EncryptionUtils.randomBytes(EncryptionUtils.saltLength))));
+            assertTrue(ivs.add(encodeBytesToBase64String(
+                randomBytes(saltLength))));
         }
     }
 
@@ -263,13 +401,13 @@ public class EncryptionTestIT {
     public void testSHA512() {
         // sent to 3rd party app in cleartext
         String token = "4ae5978bf5354cd284b539015d442141";
-        String salt = EncryptionUtils.encodeBytesToBase64String(EncryptionUtils.randomBytes(EncryptionUtils.saltLength));
+        String salt = encodeBytesToBase64String(randomBytes(saltLength));
 
         // stored in database
-        String hashedToken = EncryptionUtils.generateSHA512(token, salt);
+        String hashedToken = generateSHA512(token, salt);
 
         // check: use passed cleartext and salt to verify hashed token
-        assertTrue(EncryptionUtils.verifySHA512(hashedToken, token));
+        assertTrue(verifySHA512(hashedToken, token));
     }
 
 
@@ -289,9 +427,9 @@ public class EncryptionTestIT {
     }
 
     private DecryptedFolderMetadata generateFolderMetadata() throws Exception {
-        String metadataKey0 = EncryptionUtils.encodeBytesToBase64String(EncryptionUtils.generateKey());
-        String metadataKey1 = EncryptionUtils.encodeBytesToBase64String(EncryptionUtils.generateKey());
-        String metadataKey2 = EncryptionUtils.encodeBytesToBase64String(EncryptionUtils.generateKey());
+        String metadataKey0 = encodeBytesToBase64String(generateKey());
+        String metadataKey1 = encodeBytesToBase64String(generateKey());
+        String metadataKey2 = encodeBytesToBase64String(generateKey());
         HashMap<Integer, String> metadataKeys = new HashMap<>();
         metadataKeys.put(0, EncryptionUtils.encryptStringAsymmetric(metadataKey0, cert));
         metadataKeys.put(1, EncryptionUtils.encryptStringAsymmetric(metadataKey1, cert));
@@ -345,28 +483,28 @@ public class EncryptionTestIT {
     private boolean cryptFile(String fileName, String md5, byte[] key, byte[] iv, byte[] expectedAuthTag)
             throws Exception {
         File file = getFile(fileName);
-        assertEquals(md5, EncryptionUtils.getMD5Sum(file));
+        assertEquals(md5, getMD5Sum(file));
 
-        EncryptionUtils.EncryptedFile encryptedFile = EncryptionUtils.encryptFile(file, key, iv);
+        EncryptedFile encryptedFile = encryptFile(file, key, iv);
 
         File encryptedTempFile = File.createTempFile("file", "tmp");
         FileOutputStream fileOutputStream = new FileOutputStream(encryptedTempFile);
         fileOutputStream.write(encryptedFile.encryptedBytes);
         fileOutputStream.close();
 
-        byte[] authenticationTag = EncryptionUtils.decodeStringToBase64Bytes(encryptedFile.authenticationTag);
+        byte[] authenticationTag = decodeStringToBase64Bytes(encryptedFile.authenticationTag);
 
         // verify authentication tag
         assertTrue(Arrays.equals(expectedAuthTag, authenticationTag));
 
-        byte[] decryptedBytes = EncryptionUtils.decryptFile(encryptedTempFile, key, iv, authenticationTag);
+        byte[] decryptedBytes = decryptFile(encryptedTempFile, key, iv, authenticationTag);
 
         File decryptedFile = File.createTempFile("file", "dec");
         FileOutputStream fileOutputStream1 = new FileOutputStream(decryptedFile);
         fileOutputStream1.write(decryptedBytes);
         fileOutputStream1.close();
 
-        return md5.compareTo(EncryptionUtils.getMD5Sum(decryptedFile)) == 0;
+        return md5.compareTo(getMD5Sum(decryptedFile)) == 0;
     }
 
     private File getFile(String filename) throws IOException {

+ 2 - 1
src/androidTest/java/com/owncloud/android/util/ErrorMessageAdapterIT.java

@@ -25,6 +25,7 @@ import android.accounts.Account;
 import android.content.res.Resources;
 
 import com.owncloud.android.MainApp;
+import com.owncloud.android.datamodel.OCFile;
 import com.owncloud.android.lib.common.operations.RemoteOperationResult;
 import com.owncloud.android.operations.RemoveFileOperation;
 import com.owncloud.android.utils.ErrorMessageAdapter;
@@ -50,7 +51,7 @@ public class ErrorMessageAdapterIT {
 
         String errorMessage = ErrorMessageAdapter.getErrorCauseMessage(
             new RemoteOperationResult(RemoteOperationResult.ResultCode.FORBIDDEN),
-            new RemoveFileOperation(PATH_TO_DELETE, false, account, false, MainApp.getAppContext()),
+            new RemoveFileOperation(new OCFile(PATH_TO_DELETE), false, account, false, MainApp.getAppContext()),
             resources
         );
 

+ 1 - 0
src/main/java/com/nextcloud/client/account/CurrentAccountProvider.java

@@ -13,6 +13,7 @@ import androidx.annotation.Nullable;
 public interface CurrentAccountProvider {
     /**
      * Get currently active account.
+     * Replaced by getUser()
      *
      * @return Currently selected {@link Account} or first valid {@link Account} registered in OS or null, if not available at all.
      */

+ 1 - 1
src/main/java/com/nextcloud/client/jobs/OfflineSyncWork.kt

@@ -93,7 +93,7 @@ class OfflineSyncWork constructor(
         val ocFolder = storageManager.getFileByPath(folderName)
         Log_OC.d(TAG, folderName + ": currentEtag: " + ocFolder.etag)
         // check for etag change, if false, skip
-        val checkEtagOperation = CheckEtagRemoteOperation(ocFolder.remotePath,
+        val checkEtagOperation = CheckEtagRemoteOperation(ocFolder.encryptedFileName,
             ocFolder.etagOnServer)
         val result = checkEtagOperation.execute(user.toPlatformAccount(), context)
         when (result.code) {

+ 40 - 25
src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java

@@ -96,8 +96,25 @@ public class FileDataStorageManager {
         this.account = account;
     }
 
+
+    /**
+     * Use getFileByEncryptedRemotePath() or getFileByDecryptedRemotePath()
+     */
+    @Deprecated
     public OCFile getFileByPath(String path) {
-        Cursor c = getFileCursorForValue(ProviderTableMeta.FILE_PATH, path);
+        return getFileByEncryptedRemotePath(path);
+    }
+
+    public OCFile getFileByEncryptedRemotePath(String path) {
+        return getFileByPath(ProviderTableMeta.FILE_PATH, path);
+    }
+
+    public OCFile getFileByDecryptedRemotePath(String path) {
+        return getFileByPath(ProviderTableMeta.FILE_PATH_DECRYPTED, path);
+    }
+
+    private OCFile getFileByPath(String type, String path) {
+        Cursor c = getFileCursorForValue(type, path);
         OCFile file = null;
         if (c.moveToFirst()) {
             file = createFileInstance(c);
@@ -190,8 +207,9 @@ public class FileDataStorageManager {
         cv.put(ProviderTableMeta.FILE_ENCRYPTED_NAME, file.getEncryptedFileName());
         cv.put(ProviderTableMeta.FILE_PARENT, file.getParentId());
         cv.put(ProviderTableMeta.FILE_PATH, file.getRemotePath());
+        cv.put(ProviderTableMeta.FILE_PATH_DECRYPTED, file.getDecryptedRemotePath());
+        cv.put(ProviderTableMeta.FILE_IS_ENCRYPTED, file.isEncrypted());
         if (!file.isFolder()) {
-            cv.put(ProviderTableMeta.FILE_IS_ENCRYPTED, file.isEncrypted());
             cv.put(ProviderTableMeta.FILE_STORAGE_PATH, file.getStoragePath());
         }
         cv.put(ProviderTableMeta.FILE_ACCOUNT_OWNER, account.name);
@@ -215,10 +233,7 @@ public class FileDataStorageManager {
         cv.put(ProviderTableMeta.FILE_RICH_WORKSPACE, file.getRichWorkspace());
 
         boolean sameRemotePath = fileExists(file.getRemotePath());
-        if (sameRemotePath ||
-                fileExists(file.getFileId())) {  // for renamed files; no more delete and create
-
-
+        if (sameRemotePath || fileExists(file.getFileId())) {  // for renamed files; no more delete and create
             if (sameRemotePath) {
                 OCFile oldFile = getFileByPath(file.getRemotePath());
                 file.setFileId(oldFile.getFileId());
@@ -446,6 +461,7 @@ public class FileDataStorageManager {
         cv.put(ProviderTableMeta.FILE_NAME, folder.getFileName());
         cv.put(ProviderTableMeta.FILE_PARENT, folder.getParentId());
         cv.put(ProviderTableMeta.FILE_PATH, folder.getRemotePath());
+        cv.put(ProviderTableMeta.FILE_PATH_DECRYPTED, folder.getDecryptedRemotePath());
         cv.put(ProviderTableMeta.FILE_ACCOUNT_OWNER, account.name);
         cv.put(ProviderTableMeta.FILE_LAST_SYNC_DATE, folder.getLastSyncDateForProperties());
         cv.put(ProviderTableMeta.FILE_LAST_SYNC_DATE_FOR_DATA, folder.getLastSyncDateForData());
@@ -479,9 +495,8 @@ public class FileDataStorageManager {
         cv.put(ProviderTableMeta.FILE_ENCRYPTED_NAME, file.getEncryptedFileName());
         cv.put(ProviderTableMeta.FILE_PARENT, folder.getFileId());
         cv.put(ProviderTableMeta.FILE_PATH, file.getRemotePath());
-        if (!file.isFolder()) {
-            cv.put(ProviderTableMeta.FILE_STORAGE_PATH, file.getStoragePath());
-        }
+        cv.put(ProviderTableMeta.FILE_PATH_DECRYPTED, file.getDecryptedRemotePath());
+        cv.put(ProviderTableMeta.FILE_STORAGE_PATH, file.getStoragePath());
         cv.put(ProviderTableMeta.FILE_ACCOUNT_OWNER, account.name);
         cv.put(ProviderTableMeta.FILE_LAST_SYNC_DATE, file.getLastSyncDateForProperties());
         cv.put(ProviderTableMeta.FILE_LAST_SYNC_DATE_FOR_DATA, file.getLastSyncDateForData());
@@ -659,11 +674,11 @@ public class FileDataStorageManager {
             if (getContentProviderClient() != null) {
                 try {
                     c = getContentProviderClient().query(
-                            ProviderTableMeta.CONTENT_URI,
-                            null,
-                            ProviderTableMeta.FILE_ACCOUNT_OWNER + AND + ProviderTableMeta.FILE_PATH + " LIKE ? ",
-                            new String[]{account.name, file.getRemotePath() + "%"},
-                            ProviderTableMeta.FILE_PATH + " ASC "
+                        ProviderTableMeta.CONTENT_URI,
+                        null,
+                        ProviderTableMeta.FILE_ACCOUNT_OWNER + AND + ProviderTableMeta.FILE_PATH + " LIKE ? ",
+                        new String[]{account.name, file.getRemotePath() + "%"},
+                        ProviderTableMeta.FILE_PATH + " ASC "
                     );
                 } catch (RemoteException e) {
                     Log_OC.e(TAG, e.getMessage(), e);
@@ -671,11 +686,11 @@ public class FileDataStorageManager {
 
             } else {
                 c = getContentResolver().query(
-                        ProviderTableMeta.CONTENT_URI,
-                        null,
-                        ProviderTableMeta.FILE_ACCOUNT_OWNER + AND + ProviderTableMeta.FILE_PATH + " LIKE ? ",
-                        new String[]{account.name, file.getRemotePath() + "%"},
-                        ProviderTableMeta.FILE_PATH + " ASC "
+                    ProviderTableMeta.CONTENT_URI,
+                    null,
+                    ProviderTableMeta.FILE_ACCOUNT_OWNER + AND + ProviderTableMeta.FILE_PATH + " LIKE ? ",
+                    new String[]{account.name, file.getRemotePath() + "%"},
+                    ProviderTableMeta.FILE_PATH + " ASC "
                 );
             }
 
@@ -692,8 +707,8 @@ public class FileDataStorageManager {
                     ContentValues cv = new ContentValues(); // keep construction in the loop
                     OCFile child = createFileInstance(c);
                     cv.put(
-                            ProviderTableMeta.FILE_PATH,
-                            targetPath + child.getRemotePath().substring(lengthOfOldPath)
+                        ProviderTableMeta.FILE_PATH,
+                        targetPath + child.getRemotePath().substring(lengthOfOldPath)
                     );
                     if (child.getStoragePath() != null && child.getStoragePath().startsWith(defaultSavePath)) {
                         // update link to downloaded content - but local move is not done here!
@@ -961,9 +976,9 @@ public class FileDataStorageManager {
         OCFile file = null;
         if (c != null) {
             file = new OCFile(c.getString(c.getColumnIndex(ProviderTableMeta.FILE_PATH)));
+            file.setDecryptedRemotePath(getString(c, ProviderTableMeta.FILE_PATH_DECRYPTED));
             file.setFileId(c.getLong(c.getColumnIndex(ProviderTableMeta._ID)));
             file.setParentId(c.getLong(c.getColumnIndex(ProviderTableMeta.FILE_PARENT)));
-            file.setEncryptedFileName(c.getString(c.getColumnIndex(ProviderTableMeta.FILE_ENCRYPTED_NAME)));
             file.setMimeType(c.getString(c.getColumnIndex(ProviderTableMeta.FILE_CONTENT_TYPE)));
             file.setStoragePath(c.getString(c.getColumnIndex(ProviderTableMeta.FILE_STORAGE_PATH)));
             if (file.getStoragePath() == null) {
@@ -995,9 +1010,9 @@ public class FileDataStorageManager {
             file.setEtagInConflict(c.getString(c.getColumnIndex(ProviderTableMeta.FILE_ETAG_IN_CONFLICT)));
             file.setFavorite(c.getInt(c.getColumnIndex(ProviderTableMeta.FILE_FAVORITE)) == 1);
             file.setEncrypted(c.getInt(c.getColumnIndex(ProviderTableMeta.FILE_IS_ENCRYPTED)) == 1);
-            if (file.isEncrypted()) {
-                file.setFileName(c.getString(c.getColumnIndex(ProviderTableMeta.FILE_NAME)));
-            }
+//            if (file.isEncrypted()) {
+//                file.setFileName(c.getString(c.getColumnIndex(ProviderTableMeta.FILE_NAME)));
+//            }
             file.setMountType(WebdavEntry.MountType.values()[c.getInt(
                     c.getColumnIndex(ProviderTableMeta.FILE_MOUNT_TYPE))]);
             file.setPreviewAvailable(c.getInt(c.getColumnIndex(ProviderTableMeta.FILE_HAS_PREVIEW)) == 1);

+ 73 - 35
src/main/java/com/owncloud/android/datamodel/OCFile.java

@@ -64,6 +64,7 @@ public class OCFile implements Parcelable, Comparable<OCFile>, ServerFileInterfa
      */
     private long modificationTimestampAtLastSyncForData;
     private String remotePath;
+    private String decryptedRemotePath;
     private String localPath;
     private String mimeType;
     private boolean needsUpdatingWhileSaving;
@@ -103,8 +104,6 @@ public class OCFile implements Parcelable, Comparable<OCFile>, ServerFileInterfa
      * Cached after first call, until changed.
      */
     private Uri exposedFileUri;
-    private String encryptedFileName;
-
 
     /**
      * Create new {@link OCFile} with given path.
@@ -135,6 +134,7 @@ public class OCFile implements Parcelable, Comparable<OCFile>, ServerFileInterfa
         modificationTimestamp = source.readLong();
         modificationTimestampAtLastSyncForData = source.readLong();
         remotePath = source.readString();
+        decryptedRemotePath = source.readString();
         localPath = source.readString();
         mimeType = source.readString();
         needsUpdatingWhileSaving = source.readInt() == 0;
@@ -152,7 +152,6 @@ public class OCFile implements Parcelable, Comparable<OCFile>, ServerFileInterfa
         sharedWithSharee = source.readInt() == 1;
         favorite = source.readInt() == 1;
         encrypted = source.readInt() == 1;
-        encryptedFileName = source.readString();
         ownerId = source.readString();
         ownerDisplayName = source.readString();
         mountType = (WebdavEntry.MountType) source.readSerializable();
@@ -169,6 +168,7 @@ public class OCFile implements Parcelable, Comparable<OCFile>, ServerFileInterfa
         dest.writeLong(modificationTimestamp);
         dest.writeLong(modificationTimestampAtLastSyncForData);
         dest.writeString(remotePath);
+        dest.writeString(decryptedRemotePath);
         dest.writeString(localPath);
         dest.writeString(mimeType);
         dest.writeInt(needsUpdatingWhileSaving ? 1 : 0);
@@ -186,7 +186,6 @@ public class OCFile implements Parcelable, Comparable<OCFile>, ServerFileInterfa
         dest.writeInt(sharedWithSharee ? 1 : 0);
         dest.writeInt(favorite ? 1 : 0);
         dest.writeInt(encrypted ? 1 : 0);
-        dest.writeString(encryptedFileName);
         dest.writeString(ownerId);
         dest.writeString(ownerDisplayName);
         dest.writeSerializable(mountType);
@@ -194,34 +193,50 @@ public class OCFile implements Parcelable, Comparable<OCFile>, ServerFileInterfa
         dest.writeInt(previewAvailable ? 1 : 0);
     }
 
+    public void setDecryptedRemotePath(String path) {
+        decryptedRemotePath = path;
+    }
+
+    /**
+     * Use decrypted remote path for every local file operation Use encrypted remote path for every dav related
+     * operation
+     */
     public String getDecryptedRemotePath() {
-        return remotePath;
+        // Fallback
+        // TODO test without, on a new created folder
+        if (!isEncrypted() && decryptedRemotePath == null) {
+            decryptedRemotePath = remotePath;
+        }
+
+        if (isFolder()) {
+            if (decryptedRemotePath.endsWith(PATH_SEPARATOR)) {
+                return decryptedRemotePath;
+            } else {
+                return decryptedRemotePath + PATH_SEPARATOR;
+            }
+        } else {
+            return decryptedRemotePath;
+        }
     }
 
     /**
-     * Returns the remote path of the file on ownCloud
+     * Returns the remote path of the file on Nextcloud
+     * (this might be an encrypted file path, if E2E is used)
+     * <p>
+     * Use decrypted remote path for every local file operation.
+     * Use remote path for every dav related operation
      *
      * @return The remote path to the file
      */
     public String getRemotePath() {
-        if (isEncrypted() && !isFolder()) {
-            String parentPath = new File(remotePath).getParent();
-
-            if (parentPath.endsWith(PATH_SEPARATOR)) {
-                return parentPath + getEncryptedFileName();
+        if (isFolder()) {
+            if (remotePath.endsWith(PATH_SEPARATOR)) {
+                return remotePath;
             } else {
-                return parentPath + PATH_SEPARATOR + getEncryptedFileName();
+                return remotePath + PATH_SEPARATOR;
             }
         } else {
-            if (isFolder()) {
-                if (remotePath.endsWith(PATH_SEPARATOR)) {
-                    return remotePath;
-                } else {
-                    return remotePath + PATH_SEPARATOR;
-                }
-            } else {
-                return remotePath;
-            }
+            return remotePath;
         }
     }
 
@@ -241,7 +256,7 @@ public class OCFile implements Parcelable, Comparable<OCFile>, ServerFileInterfa
      * @return true if it is a folder
      */
     public boolean isFolder() {
-        return MimeType.DIRECTORY.equals(mimeType);
+        return MimeType.DIRECTORY.equals(mimeType) || MimeType.WEBDAV_FOLDER.equals(mimeType);
     }
 
 
@@ -346,17 +361,40 @@ public class OCFile implements Parcelable, Comparable<OCFile>, ServerFileInterfa
      * @param storage_path to set
      */
     public void setStoragePath(String storage_path) {
-        localPath = storage_path;
+        if (storage_path == null) {
+            localPath = null;
+        } else {
+            localPath = storage_path.replaceAll("//", "/");
+        }
         localUri = null;
         exposedFileUri = null;
     }
 
     /**
-     * Returns the filename and "/" for the root directory
+     * Returns the decrypted filename and "/" for the root directory
      *
      * @return The name of the file
      */
     public String getFileName() {
+        return getDecryptedFileName();
+    }
+
+    /**
+     * Returns the decrypted filename and "/" for the root directory
+     *
+     * @return The name of the file
+     */
+    public String getDecryptedFileName() {
+        File f = new File(getDecryptedRemotePath());
+        return f.getName().length() == 0 ? ROOT_PATH : f.getName();
+    }
+
+    /**
+     * Returns the encrypted filename and "/" for the root directory
+     *
+     * @return The name of the file
+     */
+    public String getEncryptedFileName() {
         File f = new File(remotePath);
         return f.getName().length() == 0 ? ROOT_PATH : f.getName();
     }
@@ -386,6 +424,7 @@ public class OCFile implements Parcelable, Comparable<OCFile>, ServerFileInterfa
     private void resetData() {
         fileId = -1;
         remotePath = null;
+        decryptedRemotePath = null;
         parentId = 0;
         localPath = null;
         mimeType = null;
@@ -408,7 +447,6 @@ public class OCFile implements Parcelable, Comparable<OCFile>, ServerFileInterfa
         sharedWithSharee = false;
         favorite = false;
         encrypted = false;
-        encryptedFileName = null;
         mountType = WebdavEntry.MountType.INTERNAL;
         richWorkspace = "";
     }
@@ -464,8 +502,16 @@ public class OCFile implements Parcelable, Comparable<OCFile>, ServerFileInterfa
     public String toString() {
         String asString = "[id=%s, name=%s, mime=%s, downloaded=%s, local=%s, remote=%s, " +
                 "parentId=%s, etag=%s, favourite=%s]";
-        return String.format(asString, fileId, getFileName(), mimeType, isDown(), localPath, remotePath, parentId,
-            etag, favorite);
+        return String.format(asString,
+                             fileId,
+                             getFileName(),
+                             mimeType,
+                             isDown(),
+                             localPath,
+                             remotePath,
+                             parentId,
+                             etag,
+                             favorite);
     }
 
     public void setEtag(String etag) {
@@ -652,10 +698,6 @@ public class OCFile implements Parcelable, Comparable<OCFile>, ServerFileInterfa
         return this.richWorkspace;
     }
 
-    public String getEncryptedFileName() {
-        return this.encryptedFileName;
-    }
-
     public void setFileId(long fileId) {
         this.fileId = fileId;
     }
@@ -767,8 +809,4 @@ public class OCFile implements Parcelable, Comparable<OCFile>, ServerFileInterfa
     public void setRichWorkspace(String richWorkspace) {
         this.richWorkspace = richWorkspace;
     }
-
-    public void setEncryptedFileName(String encryptedFileName) {
-        this.encryptedFileName = encryptedFileName;
-    }
 }

+ 2 - 2
src/main/java/com/owncloud/android/datamodel/ThumbnailsCacheManager.java

@@ -332,7 +332,7 @@ public final class ThumbnailsCacheManager {
                         GetMethod getMethod = null;
                         try {
                             String uri = mClient.getBaseUri() + "/index.php/core/preview.png?file="
-                                    + URLEncoder.encode(file.getRemotePath())
+                                + URLEncoder.encode(file.getRemotePath())
                                     + "&x=" + pxW + "&y=" + pxH + "&a=1&mode=cover&forceIcon=0";
                             getMethod = new GetMethod(uri);
 
@@ -619,7 +619,7 @@ public final class ThumbnailsCacheManager {
                                 String uri;
                                 if (file instanceof OCFile) {
                                     uri = mClient.getBaseUri() + "/index.php/apps/files/api/v1/thumbnail/" +
-                                            pxW + "/" + pxH + Uri.encode(file.getRemotePath(), "/");
+                                        pxW + "/" + pxH + Uri.encode(file.getRemotePath(), "/");
                                 } else {
                                     uri = mClient.getBaseUri() + "/index.php/apps/files_trashbin/preview?fileId=" +
                                             file.getLocalId() + "&x=" + pxW + "&y=" + pxH;

+ 2 - 1
src/main/java/com/owncloud/android/db/ProviderMeta.java

@@ -31,7 +31,7 @@ import com.owncloud.android.MainApp;
  */
 public class ProviderMeta {
     public static final String DB_NAME = "filelist";
-    public static final int DB_VERSION = 55;
+    public static final int DB_VERSION = 56;
 
     private ProviderMeta() {
         // No instance
@@ -89,6 +89,7 @@ public class ProviderMeta {
         public static final String FILE_CONTENT_TYPE = "content_type";
         public static final String FILE_STORAGE_PATH = "media_path";
         public static final String FILE_PATH = "path";
+        public static final String FILE_PATH_DECRYPTED = "path_decrypted";
         public static final String FILE_ACCOUNT_OWNER = "file_owner";
         public static final String FILE_LAST_SYNC_DATE = "last_sync_date";// _for_properties, but let's keep it as it is
         public static final String FILE_LAST_SYNC_DATE_FOR_DATA = "last_sync_date_for_data";

+ 1 - 1
src/main/java/com/owncloud/android/files/FileMenuFilter.java

@@ -280,7 +280,7 @@ public class FileMenuFilter {
                             List<Integer> toHide,
                             OCCapability capability
     ) {
-        if (deviceInfo.editorSupported()) {
+        if (deviceInfo.editorSupported() || files.iterator().next().isEncrypted()) {
             toHide.add(R.id.action_edit);
             return;
         }

+ 2 - 2
src/main/java/com/owncloud/android/files/services/FileDownloader.java

@@ -292,13 +292,13 @@ public class FileDownloader extends Service
          */
         public void cancel(Account account, OCFile file) {
             Pair<DownloadFileOperation, String> removeResult =
-                    mPendingDownloads.remove(account.name, file.getRemotePath());
+                mPendingDownloads.remove(account.name, file.getRemotePath());
             DownloadFileOperation download = removeResult.first;
             if (download != null) {
                 download.cancel();
             } else {
                 if (mCurrentDownload != null && mCurrentAccount != null &&
-                        mCurrentDownload.getRemotePath().startsWith(file.getRemotePath()) &&
+                    mCurrentDownload.getRemotePath().startsWith(file.getRemotePath()) &&
                         account.name.equals(mCurrentAccount.name)) {
                     mCurrentDownload.cancel();
                 }

+ 0 - 0
src/main/java/com/owncloud/android/jobs/OfflineSyncJob.java


+ 215 - 30
src/main/java/com/owncloud/android/operations/CreateFolderOperation.java

@@ -21,59 +21,246 @@
 
 package com.owncloud.android.operations;
 
+import android.accounts.Account;
+import android.content.Context;
+import android.os.Build;
+import android.util.Pair;
+
+import com.owncloud.android.datamodel.ArbitraryDataProvider;
+import com.owncloud.android.datamodel.DecryptedFolderMetadata;
+import com.owncloud.android.datamodel.EncryptedFolderMetadata;
 import com.owncloud.android.datamodel.OCFile;
 import com.owncloud.android.lib.common.OwnCloudClient;
 import com.owncloud.android.lib.common.operations.OnRemoteOperationListener;
 import com.owncloud.android.lib.common.operations.RemoteOperation;
 import com.owncloud.android.lib.common.operations.RemoteOperationResult;
 import com.owncloud.android.lib.common.utils.Log_OC;
+import com.owncloud.android.lib.resources.e2ee.ToggleEncryptionRemoteOperation;
 import com.owncloud.android.lib.resources.files.CreateFolderRemoteOperation;
 import com.owncloud.android.lib.resources.files.ReadFolderRemoteOperation;
 import com.owncloud.android.lib.resources.files.model.RemoteFile;
 import com.owncloud.android.operations.common.SyncOperation;
+import com.owncloud.android.utils.EncryptionUtils;
 import com.owncloud.android.utils.FileStorageUtils;
 import com.owncloud.android.utils.MimeType;
 
+import java.io.File;
+import java.util.UUID;
+
+import androidx.annotation.RequiresApi;
+
 import static com.owncloud.android.datamodel.OCFile.PATH_SEPARATOR;
 import static com.owncloud.android.datamodel.OCFile.ROOT_PATH;
 
 
 /**
- * Access to remote operation performing the creation of a new folder in the ownCloud server.
- * Save the new folder in Database
+ * Access to remote operation performing the creation of a new folder in the ownCloud server. Save the new folder in
+ * Database
  */
-public class CreateFolderOperation extends SyncOperation implements OnRemoteOperationListener{
+public class CreateFolderOperation extends SyncOperation implements OnRemoteOperationListener {
 
     private static final String TAG = CreateFolderOperation.class.getSimpleName();
 
-    protected String mRemotePath;
-    private boolean mCreateFullPath;
+    protected String remotePath;
     private RemoteFile createdRemoteFolder;
+    private Account account;
+    private Context context;
 
     /**
      * Constructor
-     *
-     * @param createFullPath        'True' means that all the ancestor folders should be created
-     *                              if don't exist yet.
      */
-    public CreateFolderOperation(String remotePath, boolean createFullPath) {
-        mRemotePath = remotePath;
-        mCreateFullPath = createFullPath;
+    public CreateFolderOperation(String remotePath, Account account, Context context) {
+        this.remotePath = remotePath;
+        this.account = account;
+        this.context = context;
     }
 
-
     @Override
     protected RemoteOperationResult run(OwnCloudClient client) {
-        RemoteOperationResult result = new CreateFolderRemoteOperation(mRemotePath, mCreateFullPath).execute(client);
+        String remoteParentPath = new File(getRemotePath()).getParent();
+        remoteParentPath = remoteParentPath.endsWith(OCFile.PATH_SEPARATOR) ?
+            remoteParentPath : remoteParentPath + OCFile.PATH_SEPARATOR;
+
+        OCFile parent = getStorageManager().getFileByDecryptedRemotePath(remoteParentPath);
+
+        // check if any parent is encrypted
+        boolean encryptedAncestor = FileStorageUtils.checkEncryptionStatus(parent, getStorageManager());
+
+        if (encryptedAncestor) {
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
+                return encryptedCreate(parent, remoteParentPath, client);
+            } else {
+                Log_OC.e(TAG, "Encrypted upload on old Android API");
+                return new RemoteOperationResult(RemoteOperationResult.ResultCode.OLD_ANDROID_API);
+            }
+        } else {
+            return normalCreate(client);
+        }
+    }
+
+    @RequiresApi(api = Build.VERSION_CODES.KITKAT)
+    private RemoteOperationResult encryptedCreate(OCFile parent, String remoteParentPath, OwnCloudClient client) {
+        ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProvider(context.getContentResolver());
+        String privateKey = arbitraryDataProvider.getValue(account.name, EncryptionUtils.PRIVATE_KEY);
+        String publicKey = arbitraryDataProvider.getValue(account.name, EncryptionUtils.PUBLIC_KEY);
+
+        String token = null;
+        Boolean metadataExists;
+        DecryptedFolderMetadata metadata;
+        String encryptedRemotePath = null;
+
+        String filename = new File(remotePath).getName();
+
+        try {
+            // lock folder
+            token = EncryptionUtils.lockFolder(parent, client);
+
+            // get metadata
+            Pair<Boolean, DecryptedFolderMetadata> metadataPair = EncryptionUtils.retrieveMetadata(parent,
+                                                                                                   client,
+                                                                                                   privateKey,
+                                                                                                   publicKey);
+
+            metadataExists = metadataPair.first;
+            metadata = metadataPair.second;
+
+            // check if filename already exists
+            for (String key : metadata.getFiles().keySet()) {
+                DecryptedFolderMetadata.DecryptedFile file = metadata.getFiles().get(key);
+
+                if (file != null && filename.equalsIgnoreCase(file.getEncrypted().getFilename())) {
+                    return new RemoteOperationResult(RemoteOperationResult.ResultCode.FOLDER_ALREADY_EXISTS);
+                }
+            }
+
+            // generate new random file name, check if it exists in metadata
+            String encryptedFileName = UUID.randomUUID().toString().replaceAll("-", "");
+
+            while (metadata.getFiles().get(encryptedFileName) != null) {
+                encryptedFileName = UUID.randomUUID().toString().replaceAll("-", "");
+            }
+            encryptedRemotePath = parent.getRemotePath() + encryptedFileName;
+
+            RemoteOperationResult result = new CreateFolderRemoteOperation(encryptedRemotePath,
+                                                                           true,
+                                                                           token)
+                .execute(client);
+
+            if (result.isSuccess()) {
+                RemoteOperationResult remoteFolderOperationResult = new ReadFolderRemoteOperation(encryptedRemotePath)
+                    .execute(client);
+
+                createdRemoteFolder = (RemoteFile) remoteFolderOperationResult.getData().get(0);
+                OCFile newDir = new OCFile(createdRemoteFolder.getRemotePath());
+                newDir.setMimeType(MimeType.DIRECTORY);
+
+                newDir.setParentId(parent.getFileId());
+                newDir.setRemoteId(createdRemoteFolder.getRemoteId());
+                newDir.setModificationTimestamp(System.currentTimeMillis());
+                newDir.setEncrypted(true);
+                newDir.setPermissions(createdRemoteFolder.getPermissions());
+                newDir.setDecryptedRemotePath(parent.getDecryptedRemotePath() + filename + "/");
+                getStorageManager().saveFile(newDir);
+
+                RemoteOperationResult encryptionOperationResult = new ToggleEncryptionRemoteOperation(
+                    newDir.getLocalId(),
+                    newDir.getRemotePath(),
+                    true)
+                    .execute(client);
+
+                if (!encryptionOperationResult.isSuccess()) {
+                    throw new RuntimeException("Error creating encrypted subfolder!");
+                }
+
+                // Key, always generate new one
+                byte[] key = EncryptionUtils.generateKey();
+
+                // IV, always generate new one
+                byte[] iv = EncryptionUtils.randomBytes(EncryptionUtils.ivLength);
+
+                // update metadata
+                DecryptedFolderMetadata.DecryptedFile decryptedFile = new DecryptedFolderMetadata.DecryptedFile();
+                DecryptedFolderMetadata.Data data = new DecryptedFolderMetadata.Data();
+                data.setFilename(filename);
+                data.setMimetype(MimeType.WEBDAV_FOLDER);
+                data.setKey(EncryptionUtils.encodeBytesToBase64String(key));
+
+                decryptedFile.setEncrypted(data);
+                decryptedFile.setInitializationVector(EncryptionUtils.encodeBytesToBase64String(iv));
+
+                metadata.getFiles().put(encryptedFileName, decryptedFile);
+
+                EncryptedFolderMetadata encryptedFolderMetadata = EncryptionUtils.encryptFolderMetadata(metadata,
+                                                                                                        privateKey);
+                String serializedFolderMetadata = EncryptionUtils.serializeJSON(encryptedFolderMetadata);
+
+                // upload metadata
+                EncryptionUtils.uploadMetadata(parent,
+                                               serializedFolderMetadata,
+                                               token,
+                                               client,
+                                               metadataExists);
+            } else {
+                // revert to sane state in case of any error
+                Log_OC.e(TAG, remotePath + " hasn't been created");
+            }
+
+            return result;
+        } catch (Exception e) {
+//            // revert to latest metadata
+//            try {
+//                Pair<Boolean, DecryptedFolderMetadata> metadataPair = EncryptionUtils.retrieveMetadata(parent,
+//                                                                                                       client,
+//                                                                                                       privateKey,
+//                                                                                                       publicKey);
+//            } catch (Exception metadataException) {
+//                throw new RuntimeException(metadataException);
+//            }
+
+            if (!EncryptionUtils.unlockFolder(parent, client, token).isSuccess()) {
+                throw new RuntimeException("Could not clean up after failing folder creation!");
+            }
+
+            // remove folder
+            if (encryptedRemotePath != null) {
+                RemoteOperationResult removeResult = new RemoveRemoteEncryptedFileOperation(encryptedRemotePath,
+                                                                                            parent.getLocalId(),
+                                                                                            account,
+                                                                                            context,
+                                                                                            filename).execute(client);
+
+                if (!removeResult.isSuccess()) {
+                    throw new RuntimeException("Could not clean up after failing folder creation!");
+                }
+            }
+
+            // TODO do better
+            return new RemoteOperationResult(e);
+        } finally {
+
+            // unlock folder
+            if (token != null) {
+                RemoteOperationResult unlockFolderResult = EncryptionUtils.unlockFolder(parent, client, token);
+
+                if (!unlockFolderResult.isSuccess()) {
+                    // TODO do better
+                    throw new RuntimeException("Could not unlock folder!");
+                }
+            }
+        }
+    }
+
+    private RemoteOperationResult normalCreate(OwnCloudClient client) {
+        RemoteOperationResult result = new CreateFolderRemoteOperation(remotePath, true).execute(client);
 
         if (result.isSuccess()) {
-            RemoteOperationResult remoteFolderOperationResult = new ReadFolderRemoteOperation(mRemotePath)
+            RemoteOperationResult remoteFolderOperationResult = new ReadFolderRemoteOperation(remotePath)
                 .execute(client);
 
             createdRemoteFolder = (RemoteFile) remoteFolderOperationResult.getData().get(0);
             saveFolderInDB();
         } else {
-            Log_OC.e(TAG, mRemotePath + " hasn't been created");
+            Log_OC.e(TAG, remotePath + " hasn't been created");
         }
 
         return result;
@@ -87,36 +274,34 @@ public class CreateFolderOperation extends SyncOperation implements OnRemoteOper
     }
 
     private void onCreateRemoteFolderOperationFinish(RemoteOperationResult result) {
-       if (result.isSuccess()) {
-           saveFolderInDB();
-       } else {
-           Log_OC.e(TAG, mRemotePath + " hasn't been created");
-       }
+        if (result.isSuccess()) {
+            saveFolderInDB();
+        } else {
+            Log_OC.e(TAG, remotePath + " hasn't been created");
+        }
     }
 
     /**
      * Save new directory in local database.
      */
     private void saveFolderInDB() {
-        if (mCreateFullPath && getStorageManager().
-                getFileByPath(FileStorageUtils.getParentPath(mRemotePath)) == null){// When parent
-                                                                                    // of remote path
-                                                                                    // is not created
-            String[] subFolders = mRemotePath.split(PATH_SEPARATOR);
+        if (getStorageManager().getFileByPath(FileStorageUtils.getParentPath(remotePath)) == null) {
+            // When parent of remote path is not created
+            String[] subFolders = remotePath.split(PATH_SEPARATOR);
             String composedRemotePath = ROOT_PATH;
 
             // For each ancestor folders create them recursively
             for (String subFolder : subFolders) {
                 if (!subFolder.isEmpty()) {
                     composedRemotePath = composedRemotePath + subFolder + PATH_SEPARATOR;
-                    mRemotePath = composedRemotePath;
+                    remotePath = composedRemotePath;
                     saveFolderInDB();
                 }
             }
         } else { // Create directory on DB
-            OCFile newDir = new OCFile(mRemotePath);
+            OCFile newDir = new OCFile(remotePath);
             newDir.setMimeType(MimeType.DIRECTORY);
-            long parentId = getStorageManager().getFileByPath(FileStorageUtils.getParentPath(mRemotePath)).getFileId();
+            long parentId = getStorageManager().getFileByPath(FileStorageUtils.getParentPath(remotePath)).getFileId();
             newDir.setParentId(parentId);
             newDir.setRemoteId(createdRemoteFolder.getRemoteId());
             newDir.setModificationTimestamp(System.currentTimeMillis());
@@ -124,11 +309,11 @@ public class CreateFolderOperation extends SyncOperation implements OnRemoteOper
             newDir.setPermissions(createdRemoteFolder.getPermissions());
             getStorageManager().saveFile(newDir);
 
-            Log_OC.d(TAG, "Create directory " + mRemotePath + " in Database");
+            Log_OC.d(TAG, "Create directory " + remotePath + " in Database");
         }
     }
 
     public String getRemotePath() {
-        return mRemotePath;
+        return remotePath;
     }
 }

+ 6 - 2
src/main/java/com/owncloud/android/operations/DownloadFileOperation.java

@@ -87,8 +87,12 @@ public class DownloadFileOperation extends RemoteOperation {
 
     public String getSavePath() {
         if (file.getStoragePath() != null) {
+            File parentFile = new File(file.getStoragePath()).getParentFile();
+            if (parentFile != null && !parentFile.exists()) {
+                parentFile.mkdirs();
+            }
             File path = new File(file.getStoragePath());  // re-downloads should be done over the original file
-            if (path.canWrite()) {
+            if (path.canWrite() || parentFile != null && parentFile.canWrite()) {
                 return path.getAbsolutePath();
             }
         }
@@ -117,7 +121,7 @@ public class DownloadFileOperation extends RemoteOperation {
                             file.getRemotePath().lastIndexOf('.') + 1));
             } catch (IndexOutOfBoundsException e) {
                 Log_OC.e(TAG, "Trying to find out MIME type of a file without extension: " +
-                        file.getRemotePath());
+                    file.getRemotePath());
             }
         }
         if (mimeType == null) {

+ 32 - 8
src/main/java/com/owncloud/android/operations/RefreshFolderOperation.java

@@ -46,6 +46,7 @@ import com.owncloud.android.syncadapter.FileSyncAdapter;
 import com.owncloud.android.utils.DataHolderUtil;
 import com.owncloud.android.utils.EncryptionUtils;
 import com.owncloud.android.utils.FileStorageUtils;
+import com.owncloud.android.utils.MimeType;
 import com.owncloud.android.utils.MimeTypeUtil;
 
 import java.util.ArrayList;
@@ -57,6 +58,8 @@ import java.util.Vector;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
+import static com.owncloud.android.datamodel.OCFile.PATH_SEPARATOR;
+
 
 /**
  *  Remote operation performing the synchronization of the list of files contained
@@ -241,7 +244,7 @@ public class RefreshFolderOperation extends RemoteOperation {
 
         if (!mSyncFullAccount) {
             sendLocalBroadcast(
-                    EVENT_SINGLE_FOLDER_CONTENTS_SYNCED, mLocalFolder.getRemotePath(), result
+                EVENT_SINGLE_FOLDER_CONTENTS_SYNCED, mLocalFolder.getRemotePath(), result
             );
         }
 
@@ -251,7 +254,7 @@ public class RefreshFolderOperation extends RemoteOperation {
 
         if (!mSyncFullAccount) {
             sendLocalBroadcast(
-                    EVENT_SINGLE_FOLDER_SHARES_SYNCED, mLocalFolder.getRemotePath(), result
+                EVENT_SINGLE_FOLDER_SHARES_SYNCED, mLocalFolder.getRemotePath(), result
             );
         }
 
@@ -422,7 +425,7 @@ public class RefreshFolderOperation extends RemoteOperation {
         // update permission
         mLocalFolder.setPermissions(remoteFolder.getPermissions());
 
-        // update richWorkpace
+        // update richWorkspace
         mLocalFolder.setRichWorkspace(remoteFolder.getRichWorkspace());
 
         DecryptedFolderMetadata metadata = getDecryptedFolderMetadata(encryptedAncestor);
@@ -475,6 +478,10 @@ public class RefreshFolderOperation extends RemoteOperation {
         }
 
         // save updated contents in local database
+        // update file name for encrypted files
+        if (metadata != null) {
+            updateFileNameForEncryptedFile(metadata, mLocalFolder);
+        }
         mStorageManager.saveFolder(remoteFolder, updatedFiles, localFilesMap.values());
 
         mChildren = updatedFiles;
@@ -492,15 +499,25 @@ public class RefreshFolderOperation extends RemoteOperation {
     }
 
     private void updateFileNameForEncryptedFile(@NonNull DecryptedFolderMetadata metadata, OCFile updatedFile) {
-        updatedFile.setEncryptedFileName(updatedFile.getFileName());
         try {
             String decryptedFileName = metadata.getFiles().get(updatedFile.getFileName()).getEncrypted()
                     .getFilename();
             String mimetype = metadata.getFiles().get(updatedFile.getFileName()).getEncrypted().getMimetype();
-            updatedFile.setFileName(decryptedFileName);
+
+            OCFile parentFile = mStorageManager.getFileById(updatedFile.getParentId());
+            String decryptedRemotePath = parentFile.getDecryptedRemotePath() + decryptedFileName;
+
+            if (updatedFile.isFolder()) {
+                decryptedRemotePath += "/";
+            }
+            updatedFile.setDecryptedRemotePath(decryptedRemotePath);
 
             if (mimetype == null || mimetype.isEmpty()) {
-                updatedFile.setMimeType("application/octet-stream");
+                if (updatedFile.isFolder()) {
+                    updatedFile.setMimeType(MimeType.DIRECTORY);
+                } else {
+                    updatedFile.setMimeType("application/octet-stream");
+                }
             } else {
                 updatedFile.setMimeType(mimetype);
             }
@@ -516,7 +533,11 @@ public class RefreshFolderOperation extends RemoteOperation {
             updatedFile.setModificationTimestampAtLastSyncForData(
                     localFile.getModificationTimestampAtLastSyncForData()
             );
-            updatedFile.setStoragePath(localFile.getStoragePath());
+            if (localFile.isEncrypted()) {
+                updatedFile.setStoragePath(mLocalFolder.getRemotePath() + PATH_SEPARATOR + localFile.getFileName());
+            } else {
+                updatedFile.setStoragePath(localFile.getStoragePath());
+            }
 
             // eTag will not be updated unless file CONTENTS are synchronized
             if (!updatedFile.isFolder() && localFile.isDown() &&
@@ -555,8 +576,11 @@ public class RefreshFolderOperation extends RemoteOperation {
         for (OCFile file : localFiles) {
             String remotePath = file.getRemotePath();
 
-            if (metadata != null && !file.isFolder()) {
+            if (metadata != null) {
                 remotePath = file.getParentRemotePath() + file.getEncryptedFileName();
+                if (file.isFolder() && !remotePath.endsWith(PATH_SEPARATOR)) {
+                    remotePath = remotePath + PATH_SEPARATOR;
+                }
             }
             localFilesMap.put(remotePath, file);
         }

+ 13 - 15
src/main/java/com/owncloud/android/operations/RemoveFileOperation.java

@@ -42,7 +42,6 @@ import com.owncloud.android.utils.MimeTypeUtil;
 public class RemoveFileOperation extends SyncOperation {
 
     private OCFile fileToRemove;
-    private String remotePath;
     private boolean onlyLocalCopy;
     private Account account;
     private boolean inBackground;
@@ -52,14 +51,15 @@ public class RemoveFileOperation extends SyncOperation {
     /**
      * Constructor
      *
-     * @param remotePath            RemotePath of the OCFile instance describing the remote file or
-     *                              folder to remove from the server
-     * @param onlyLocalCopy         When 'true', and a local copy of the file exists, only this is
-     *                              removed.
+     * @param fileToRemove  OCFile instance describing the remote file or folder to remove from the server
+     * @param onlyLocalCopy When 'true', and a local copy of the file exists, only this is removed.
      */
-    public RemoveFileOperation(String remotePath, boolean onlyLocalCopy, Account account, boolean inBackground,
+    public RemoveFileOperation(OCFile fileToRemove,
+                               boolean onlyLocalCopy,
+                               Account account,
+                               boolean inBackground,
                                Context context) {
-        this.remotePath = remotePath;
+        this.fileToRemove = fileToRemove;
         this.onlyLocalCopy = onlyLocalCopy;
         this.account = account;
         this.inBackground = inBackground;
@@ -90,8 +90,6 @@ public class RemoveFileOperation extends SyncOperation {
         RemoteOperationResult result = null;
         RemoteOperation operation;
 
-        fileToRemove = getStorageManager().getFileByPath(remotePath);
-
         if (MimeTypeUtil.isImage(fileToRemove.getMimeType())) {
             // store resized image
             ThumbnailsCacheManager.generateResizedImage(fileToRemove);
@@ -99,20 +97,21 @@ public class RemoveFileOperation extends SyncOperation {
 
         boolean localRemovalFailed = false;
         if (!onlyLocalCopy) {
-
             if (fileToRemove.isEncrypted() &&
                     android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) {
                 OCFile parent = getStorageManager().getFileByPath(fileToRemove.getParentRemotePath());
-                operation = new RemoveRemoteEncryptedFileOperation(remotePath, parent.getLocalId(), account, context,
-                        fileToRemove.getEncryptedFileName());
+                operation = new RemoveRemoteEncryptedFileOperation(fileToRemove.getRemotePath(),
+                                                                   parent.getLocalId(),
+                                                                   account,
+                                                                   context,
+                                                                   fileToRemove.getEncryptedFileName());
             } else {
-                operation = new RemoveFileRemoteOperation(remotePath);
+                operation = new RemoveFileRemoteOperation(fileToRemove.getDecryptedRemotePath());
             }
             result = operation.execute(client);
             if (result.isSuccess() || result.getCode() == ResultCode.FILE_NOT_FOUND) {
                 localRemovalFailed = !(getStorageManager().removeFile(fileToRemove, true, true));
             }
-
         } else {
             localRemovalFailed = !(getStorageManager().removeFile(fileToRemove, false, true));
             if (!localRemovalFailed) {
@@ -126,5 +125,4 @@ public class RemoveFileOperation extends SyncOperation {
 
         return result;
     }
-
 }

+ 27 - 7
src/main/java/com/owncloud/android/operations/RemoveRemoteEncryptedFileOperation.java

@@ -41,8 +41,19 @@ import com.owncloud.android.lib.resources.e2ee.UpdateMetadataRemoteOperation;
 import com.owncloud.android.utils.EncryptionUtils;
 
 import org.apache.commons.httpclient.HttpStatus;
+import org.apache.commons.httpclient.NameValuePair;
 import org.apache.jackrabbit.webdav.client.methods.DeleteMethod;
 
+import java.io.IOException;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.spec.InvalidKeySpecException;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.NoSuchPaddingException;
+
 import androidx.annotation.RequiresApi;
 
 /**
@@ -68,7 +79,10 @@ public class RemoveRemoteEncryptedFileOperation extends RemoteOperation {
      * @param remotePath RemotePath of the remote file or folder to remove from the server
      * @param parentId   local id of parent folder
      */
-    RemoveRemoteEncryptedFileOperation(String remotePath, String parentId, Account account, Context context,
+    RemoveRemoteEncryptedFileOperation(String remotePath,
+                                       String parentId,
+                                       Account account,
+                                       Context context,
                                        String fileName) {
         this.remotePath = remotePath;
         this.parentId = parentId;
@@ -90,8 +104,6 @@ public class RemoveRemoteEncryptedFileOperation extends RemoteOperation {
 
         String privateKey = arbitraryDataProvider.getValue(account.name, EncryptionUtils.PRIVATE_KEY);
 
-        // unlock
-
         try {
             // Lock folder
             RemoteOperationResult lockFileOperationResult = new LockFileRemoteOperation(parentId).execute(client);
@@ -122,6 +134,7 @@ public class RemoveRemoteEncryptedFileOperation extends RemoteOperation {
 
             // delete file remote
             delete = new DeleteMethod(client.getWebdavUri() + WebdavUtils.encodePath(remotePath));
+            delete.setQueryString(new NameValuePair[]{new NameValuePair(E2E_TOKEN, token)});
             int status = client.executeMethod(delete, REMOVE_READ_TIMEOUT, REMOVE_CONNECTION_TIMEOUT);
 
             delete.getResponseBodyAsString();   // exhaust the response, although not interesting
@@ -136,8 +149,9 @@ public class RemoveRemoteEncryptedFileOperation extends RemoteOperation {
             String serializedFolderMetadata = EncryptionUtils.serializeJSON(encryptedFolderMetadata);
 
             // upload metadata
-            RemoteOperationResult uploadMetadataOperationResult = new UpdateMetadataRemoteOperation(parentId,
-                                                                                                    serializedFolderMetadata, token).execute(client);
+            RemoteOperationResult uploadMetadataOperationResult =
+                new UpdateMetadataRemoteOperation(parentId,
+                                                  serializedFolderMetadata, token).execute(client);
 
             if (!uploadMetadataOperationResult.isSuccess()) {
                 throw new RemoteOperationFailedException("Metadata not uploaded!");
@@ -145,7 +159,14 @@ public class RemoveRemoteEncryptedFileOperation extends RemoteOperation {
 
             // return success
             return result;
-        } catch (Exception e) {
+        } catch (NoSuchAlgorithmException |
+            IOException |
+            InvalidKeyException |
+            InvalidAlgorithmParameterException |
+            NoSuchPaddingException |
+            BadPaddingException |
+            IllegalBlockSizeException |
+            InvalidKeySpecException e) {
             result = new RemoteOperationResult(e);
             Log_OC.e(TAG, "Remove " + remotePath + ": " + result.getLogMessage(), e);
 
@@ -167,5 +188,4 @@ public class RemoveRemoteEncryptedFileOperation extends RemoteOperation {
 
         return result;
     }
-
 }

+ 6 - 3
src/main/java/com/owncloud/android/operations/RenameFileOperation.java

@@ -90,8 +90,11 @@ public class RenameFileOperation extends SyncOperation {
                 return new RemoteOperationResult(ResultCode.INVALID_OVERWRITE);
             }
 
-            result = new RenameFileRemoteOperation(file.getFileName(), file.getRemotePath(), newName,
-                                                   file.isFolder()).execute(client);
+            result = new RenameFileRemoteOperation(file.getFileName(),
+                                                   file.getRemotePath(),
+                                                   newName,
+                                                   file.isFolder())
+                .execute(client);
 
             if (result.isSuccess()) {
                 if (file.isFolder()) {
@@ -104,7 +107,7 @@ public class RenameFileOperation extends SyncOperation {
             }
 
         } catch (IOException e) {
-            Log_OC.e(TAG, "Rename " + file.getRemotePath() + " to " + ((newRemotePath ==null) ?
+            Log_OC.e(TAG, "Rename " + file.getRemotePath() + " to " + ((newRemotePath == null) ?
                 newName : newRemotePath) + ": " +
                     (result!= null ? result.getLogMessage() : ""), e);
         }

+ 0 - 1
src/main/java/com/owncloud/android/operations/SynchronizeFileOperation.java

@@ -21,7 +21,6 @@
 
 package com.owncloud.android.operations;
 
-import android.accounts.Account;
 import android.content.Context;
 import android.content.Intent;
 import android.text.TextUtils;

+ 3 - 3
src/main/java/com/owncloud/android/operations/UploadException.java

@@ -20,14 +20,14 @@
 
 package com.owncloud.android.operations;
 
-class UploadException extends Exception {
+public class UploadException extends Exception {
     private static final long serialVersionUID = 5931153844211429915L;
 
-    UploadException() {
+    public UploadException() {
         super();
     }
 
-    UploadException(String message) {
+    public UploadException(String message) {
         super(message);
     }
 }

+ 47 - 91
src/main/java/com/owncloud/android/operations/UploadFileOperation.java

@@ -28,8 +28,8 @@ import android.net.Uri;
 import android.os.Build;
 import android.text.TextUtils;
 import android.util.Log;
+import android.util.Pair;
 
-import com.google.gson.reflect.TypeToken;
 import com.nextcloud.client.device.BatteryStatus;
 import com.nextcloud.client.device.PowerManagementService;
 import com.nextcloud.client.network.Connectivity;
@@ -51,11 +51,7 @@ import com.owncloud.android.lib.common.operations.RemoteOperation;
 import com.owncloud.android.lib.common.operations.RemoteOperationResult;
 import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode;
 import com.owncloud.android.lib.common.utils.Log_OC;
-import com.owncloud.android.lib.resources.e2ee.GetMetadataRemoteOperation;
-import com.owncloud.android.lib.resources.e2ee.LockFileRemoteOperation;
-import com.owncloud.android.lib.resources.e2ee.StoreMetadataRemoteOperation;
 import com.owncloud.android.lib.resources.e2ee.UnlockFileRemoteOperation;
-import com.owncloud.android.lib.resources.e2ee.UpdateMetadataRemoteOperation;
 import com.owncloud.android.lib.resources.files.ChunkedFileUploadRemoteOperation;
 import com.owncloud.android.lib.resources.files.ExistenceCheckRemoteOperation;
 import com.owncloud.android.lib.resources.files.ReadFileRemoteOperation;
@@ -84,7 +80,6 @@ import java.io.RandomAccessFile;
 import java.nio.channels.FileChannel;
 import java.nio.channels.FileLock;
 import java.nio.channels.OverlappingFileLockException;
-import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Set;
 import java.util.UUID;
@@ -459,52 +454,19 @@ public class UploadFileOperation extends SyncOperation {
             }
             /***** E2E *****/
 
-            // Lock folder
-            LockFileRemoteOperation lockFileOperation = new LockFileRemoteOperation(parentFile.getLocalId());
-            RemoteOperationResult lockFileOperationResult = lockFileOperation.execute(client);
-
-            if (lockFileOperationResult.isSuccess()) {
-                token = (String) lockFileOperationResult.getData().get(0);
-                // immediately store it
-                mUpload.setFolderUnlockToken(token);
-                uploadsStorageManager.updateUpload(mUpload);
-            } else if (lockFileOperationResult.getHttpCode() == HttpStatus.SC_FORBIDDEN) {
-                throw new UploadException("Forbidden! Please try again later.)");
-            } else {
-                throw new UploadException("Unknown error!");
-            }
+            token = EncryptionUtils.lockFolder(parentFile, client);
+            // immediately store it
+            mUpload.setFolderUnlockToken(token);
+            uploadsStorageManager.updateUpload(mUpload);
 
             // Update metadata
-            GetMetadataRemoteOperation getMetadataOperation = new GetMetadataRemoteOperation(parentFile.getLocalId());
-            RemoteOperationResult getMetadataOperationResult = getMetadataOperation.execute(client);
-
-            DecryptedFolderMetadata metadata;
-
-            if (getMetadataOperationResult.isSuccess()) {
-                metadataExists = true;
-
-                // decrypt metadata
-                String serializedEncryptedMetadata = (String) getMetadataOperationResult.getData().get(0);
-
-
-                EncryptedFolderMetadata encryptedFolderMetadata = EncryptionUtils.deserializeJSON(
-                        serializedEncryptedMetadata, new TypeToken<EncryptedFolderMetadata>() {
-                        });
+            Pair<Boolean, DecryptedFolderMetadata> metadataPair = EncryptionUtils.retrieveMetadata(parentFile,
+                                                                                                   client,
+                                                                                                   privateKey,
+                                                                                                   publicKey);
 
-                metadata = EncryptionUtils.decryptFolderMetaData(encryptedFolderMetadata, privateKey);
-
-            } else if (getMetadataOperationResult.getHttpCode() == HttpStatus.SC_NOT_FOUND) {
-                // new metadata
-                metadata = new DecryptedFolderMetadata();
-                metadata.setMetadata(new DecryptedFolderMetadata.Metadata());
-                metadata.getMetadata().setMetadataKeys(new HashMap<>());
-                String metadataKey = EncryptionUtils.encodeBytesToBase64String(EncryptionUtils.generateKey());
-                String encryptedMetadataKey = EncryptionUtils.encryptStringAsymmetric(metadataKey, publicKey);
-                metadata.getMetadata().getMetadataKeys().put(0, encryptedMetadataKey);
-            } else {
-                // TODO error
-                throw new UploadException("something wrong");
-            }
+            metadataExists = metadataPair.first;
+            DecryptedFolderMetadata metadata = metadataPair.second;
 
             /**** E2E *****/
 
@@ -544,8 +506,6 @@ public class UploadFileOperation extends SyncOperation {
                 encryptedFileName = UUID.randomUUID().toString().replaceAll("-", "");
             }
 
-            mFile.setEncryptedFileName(encryptedFileName);
-
             File encryptedTempFile = File.createTempFile("encFile", encryptedFileName);
             FileOutputStream fileOutputStream = new FileOutputStream(encryptedTempFile);
             fileOutputStream.write(encryptedFile.encryptedBytes);
@@ -561,7 +521,7 @@ public class UploadFileOperation extends SyncOperation {
                 // this basically means that the file is on SD card
                 // try to copy file to temporary dir if it doesn't exist
                 String temporalPath = FileStorageUtils.getInternalTemporalPath(mAccount.name, mContext) +
-                        mFile.getRemotePath();
+                    mFile.getRemotePath();
                 mFile.setStoragePath(temporalPath);
                 temporalFile = new File(temporalPath);
 
@@ -598,12 +558,18 @@ public class UploadFileOperation extends SyncOperation {
 
                 mUploadOperation = new ChunkedFileUploadRemoteOperation(encryptedTempFile.getAbsolutePath(),
                                                                         mFile.getParentRemotePath() + encryptedFileName,
-                                                                        mFile.getMimeType(), mFile.getEtagInConflict(),
-                                                                        timeStamp, onWifiConnection);
+                                                                        mFile.getMimeType(),
+                                                                        mFile.getEtagInConflict(),
+                                                                        timeStamp,
+                                                                        onWifiConnection,
+                                                                        token);
             } else {
                 mUploadOperation = new UploadFileRemoteOperation(encryptedTempFile.getAbsolutePath(),
-                        mFile.getParentRemotePath() + encryptedFileName, mFile.getMimeType(),
-                        mFile.getEtagInConflict(), timeStamp);
+                                                                 mFile.getParentRemotePath() + encryptedFileName,
+                                                                 mFile.getMimeType(),
+                                                                 mFile.getEtagInConflict(),
+                                                                 timeStamp,
+                                                                 token);
             }
 
             for (OnDatatransferProgressListener mDataTransferListener : mDataTransferListeners) {
@@ -623,10 +589,13 @@ public class UploadFileOperation extends SyncOperation {
             }
 
             if (result.isSuccess()) {
-                // upload metadata
+                mFile.setDecryptedRemotePath(parentFile.getDecryptedRemotePath() + originalFile.getName());
+                mFile.setRemotePath(parentFile.getRemotePath() + encryptedFileName);
+
+                // update metadata
                 DecryptedFolderMetadata.DecryptedFile decryptedFile = new DecryptedFolderMetadata.DecryptedFile();
                 DecryptedFolderMetadata.Data data = new DecryptedFolderMetadata.Data();
-                data.setFilename(mFile.getFileName());
+                data.setFilename(mFile.getDecryptedFileName());
                 data.setMimetype(mFile.getMimeType());
                 data.setKey(EncryptionUtils.encodeBytesToBase64String(key));
 
@@ -641,22 +610,11 @@ public class UploadFileOperation extends SyncOperation {
                 String serializedFolderMetadata = EncryptionUtils.serializeJSON(encryptedFolderMetadata);
 
                 // upload metadata
-                RemoteOperationResult uploadMetadataOperationResult;
-                if (metadataExists) {
-                    // update metadata
-                    UpdateMetadataRemoteOperation storeMetadataOperation = new UpdateMetadataRemoteOperation(
-                        parentFile.getLocalId(), serializedFolderMetadata, token);
-                    uploadMetadataOperationResult = storeMetadataOperation.execute(client);
-                } else {
-                    // store metadata
-                    StoreMetadataRemoteOperation storeMetadataOperation = new StoreMetadataRemoteOperation(
-                        parentFile.getLocalId(), serializedFolderMetadata);
-                    uploadMetadataOperationResult = storeMetadataOperation.execute(client);
-                }
-
-                if (!uploadMetadataOperationResult.isSuccess()) {
-                    throw new UploadException();
-                }
+                EncryptionUtils.uploadMetadata(parentFile,
+                                               serializedFolderMetadata,
+                                               token,
+                                               client,
+                                               metadataExists);
             }
         } catch (FileNotFoundException e) {
             Log_OC.d(TAG, mFile.getStoragePath() + " not exists anymore");
@@ -689,15 +647,20 @@ public class UploadFileOperation extends SyncOperation {
 
         if (result.isSuccess()) {
             handleSuccessfulUpload(temporalFile, expectedFile, originalFile, client);
-            RemoteOperationResult unlockFolderResult = unlockFolder(parentFile, client, token);
-
-            if (!unlockFolderResult.isSuccess()) {
-                return unlockFolderResult;
-            }
         } else if (result.getCode() == ResultCode.SYNC_CONFLICT) {
             getStorageManager().saveConflict(mFile, mFile.getEtagInConflict());
         }
 
+        // unlock must be done always
+        // TODO check if in good state
+        RemoteOperationResult unlockFolderResult = EncryptionUtils.unlockFolder(parentFile,
+                                                                                client,
+                                                                                token);
+
+        if (!unlockFolderResult.isSuccess()) {
+            return unlockFolderResult;
+        }
+
         // delete temporal file
         if (temporalFile != null && temporalFile.exists() && !temporalFile.delete()) {
             Log_OC.e(TAG, "Could not delete temporal file " + temporalFile.getAbsolutePath());
@@ -706,14 +669,6 @@ public class UploadFileOperation extends SyncOperation {
         return result;
     }
 
-    private RemoteOperationResult unlockFolder(OCFile parentFolder, OwnCloudClient client, String token) {
-        if (token != null) {
-            return new UnlockFileRemoteOperation(parentFolder.getLocalId(), token).execute(client);
-        } else {
-            return new RemoteOperationResult(new Exception("No token available"));
-        }
-    }
-
     private RemoteOperationResult checkConditions(File originalFile) {
         RemoteOperationResult remoteOperationResult = null;
 
@@ -794,7 +749,7 @@ public class UploadFileOperation extends SyncOperation {
                 // this basically means that the file is on SD card
                 // try to copy file to temporary dir if it doesn't exist
                 String temporalPath = FileStorageUtils.getInternalTemporalPath(mAccount.name, mContext) +
-                        mFile.getRemotePath();
+                    mFile.getRemotePath();
                 mFile.setStoragePath(temporalPath);
                 temporalFile = new File(temporalPath);
 
@@ -830,12 +785,13 @@ public class UploadFileOperation extends SyncOperation {
                 boolean onWifiConnection = connectivityService.getConnectivity().isWifi();
 
                 mUploadOperation = new ChunkedFileUploadRemoteOperation(mFile.getStoragePath(),
-                                                                        mFile.getRemotePath(), mFile.getMimeType(),
+                                                                        mFile.getRemotePath(),
+                                                                        mFile.getMimeType(),
                                                                         mFile.getEtagInConflict(),
                                                                         timeStamp, onWifiConnection);
             } else {
                 mUploadOperation = new UploadFileRemoteOperation(mFile.getStoragePath(),
-                        mFile.getRemotePath(), mFile.getMimeType(), mFile.getEtagInConflict(), timeStamp);
+                                                                 mFile.getRemotePath(), mFile.getMimeType(), mFile.getEtagInConflict(), timeStamp);
             }
 
             for (OnDatatransferProgressListener mDataTransferListener : mDataTransferListeners) {
@@ -1035,7 +991,7 @@ public class UploadFileOperation extends SyncOperation {
         RemoteOperation operation = new ExistenceCheckRemoteOperation(pathToGrant, false);
         RemoteOperationResult result = operation.execute(client);
         if (!result.isSuccess() && result.getCode() == ResultCode.FILE_NOT_FOUND && mRemoteFolderToBeCreated) {
-            SyncOperation syncOp = new CreateFolderOperation(pathToGrant, true);
+            SyncOperation syncOp = new CreateFolderOperation(pathToGrant, getAccount(), getContext());
             result = syncOp.execute(client, getStorageManager());
         }
         if (result.isSuccess()) {

+ 9 - 3
src/main/java/com/owncloud/android/providers/DocumentsStorageProvider.java

@@ -472,7 +472,9 @@ public class DocumentsStorageProvider extends DocumentsProvider {
         String newDirPath = targetFolder.getRemotePath() + displayName + PATH_SEPARATOR;
         FileDataStorageManager storageManager = targetFolder.getStorageManager();
 
-        RemoteOperationResult result = new CreateFolderOperation(newDirPath, true)
+        RemoteOperationResult result = new CreateFolderOperation(newDirPath,
+                                                                 accountManager.getCurrentAccount(),
+                                                                 getContext())
             .execute(targetFolder.getClient(), storageManager);
 
         if (!result.isSuccess()) {
@@ -581,8 +583,12 @@ public class DocumentsStorageProvider extends DocumentsProvider {
 
         recursiveRevokePermission(document);
 
-        RemoteOperationResult result = new RemoveFileOperation(document.getRemotePath(), false,
-                                                               document.getAccount(), true, context)
+        OCFile file = document.getStorageManager().getFileByPath(document.getRemotePath());
+        RemoteOperationResult result = new RemoveFileOperation(file,
+                                                               false,
+                                                               document.getAccount(),
+                                                               true,
+                                                               context)
             .execute(document.getClient(), document.getStorageManager());
 
         if (!result.isSuccess()) {

+ 20 - 0
src/main/java/com/owncloud/android/providers/FileContentProvider.java

@@ -692,6 +692,7 @@ public class FileContentProvider extends ContentProvider {
                        + ProviderTableMeta.FILE_NAME + TEXT
                        + ProviderTableMeta.FILE_ENCRYPTED_NAME + TEXT
                        + ProviderTableMeta.FILE_PATH + TEXT
+                       + ProviderTableMeta.FILE_PATH_DECRYPTED + TEXT
                        + ProviderTableMeta.FILE_PARENT + INTEGER
                        + ProviderTableMeta.FILE_CREATION + INTEGER
                        + ProviderTableMeta.FILE_MODIFIED + INTEGER
@@ -2193,6 +2194,25 @@ public class FileContentProvider extends ContentProvider {
             if (!upgraded) {
                 Log_OC.i(SQL, String.format(Locale.ENGLISH, UPGRADE_VERSION_MSG, oldVersion, newVersion));
             }
+
+            if (oldVersion < 56 && newVersion >= 56) {
+                Log_OC.i(SQL, "Entering in the #56 add decrypted remote path");
+                db.beginTransaction();
+                try {
+                    // Add synced.name_collision_policy
+                    db.execSQL(ALTER_TABLE + ProviderTableMeta.FILE_TABLE_NAME +
+                                   ADD_COLUMN + ProviderTableMeta.FILE_PATH_DECRYPTED + " TEXT "); // strin
+
+                    upgraded = true;
+                    db.setTransactionSuccessful();
+                } finally {
+                    db.endTransaction();
+                }
+            }
+
+            if (!upgraded) {
+                Log_OC.i(SQL, String.format(Locale.ENGLISH, UPGRADE_VERSION_MSG, oldVersion, newVersion));
+            }
         }
     }
 }

+ 7 - 6
src/main/java/com/owncloud/android/services/OperationsService.java

@@ -92,7 +92,6 @@ public class OperationsService extends Service {
     public static final String EXTRA_REMOTE_PATH = "REMOTE_PATH";
     public static final String EXTRA_NEWNAME = "NEWNAME";
     public static final String EXTRA_REMOVE_ONLY_LOCAL = "REMOVE_LOCAL_COPY";
-    public static final String EXTRA_CREATE_FULL_PATH = "CREATE_FULL_PATH";
     public static final String EXTRA_SYNC_FILE_CONTENTS = "SYNC_FILE_CONTENTS";
     public static final String EXTRA_RESULT = "RESULT";
     public static final String EXTRA_NEW_PARENT_PATH = "NEW_PARENT_PATH";
@@ -636,17 +635,19 @@ public class OperationsService extends Service {
 
                     case ACTION_REMOVE:
                         // Remove file or folder
-                        remotePath = operationIntent.getStringExtra(EXTRA_REMOTE_PATH);
+                        OCFile file = operationIntent.getParcelableExtra(EXTRA_FILE);
                         boolean onlyLocalCopy = operationIntent.getBooleanExtra(EXTRA_REMOVE_ONLY_LOCAL, false);
                         boolean inBackground = operationIntent.getBooleanExtra(EXTRA_IN_BACKGROUND, false);
-                        operation = new RemoveFileOperation(remotePath, onlyLocalCopy, account, inBackground,
-                                getApplicationContext());
+                        operation = new RemoveFileOperation(file,
+                                                            onlyLocalCopy,
+                                                            account,
+                                                            inBackground,
+                                                            getApplicationContext());
                         break;
 
                     case ACTION_CREATE_FOLDER:
                         remotePath = operationIntent.getStringExtra(EXTRA_REMOTE_PATH);
-                        boolean createFullPath = operationIntent.getBooleanExtra(EXTRA_CREATE_FULL_PATH, true);
-                        operation = new CreateFolderOperation(remotePath, createFullPath);
+                        operation = new CreateFolderOperation(remotePath, account, getApplicationContext());
                         break;
 
                     case ACTION_SYNC_FILE:

+ 2 - 2
src/main/java/com/owncloud/android/services/SyncFolderHandler.java

@@ -151,14 +151,14 @@ class SyncFolderHandler extends Handler {
             return;
         }
         Pair<SynchronizeFolderOperation, String> removeResult = mPendingOperations.remove(account.name,
-                file.getRemotePath());
+                                                                                          file.getRemotePath());
         SynchronizeFolderOperation synchronization = removeResult.first;
         if (synchronization != null) {
             synchronization.cancel();
         } else {
             // TODO synchronize?
             if (mCurrentSyncOperation != null && mCurrentAccount != null &&
-                    mCurrentSyncOperation.getRemotePath().startsWith(file.getRemotePath()) &&
+                mCurrentSyncOperation.getRemotePath().startsWith(file.getRemotePath()) &&
                     account.name.equals(mCurrentAccount.name)) {
                 mCurrentSyncOperation.cancel();
             }

+ 1 - 1
src/main/java/com/owncloud/android/syncadapter/FileSyncAdapter.java

@@ -347,7 +347,7 @@ public class FileSyncAdapter extends AbstractOwnCloudSyncAdapter {
 
         if (mCancellation && i <files.size()) {
             Log_OC.d(TAG,
-                    "Leaving synchronization before synchronizing " + files.get(i).getRemotePath() +
+                     "Leaving synchronization before synchronizing " + files.get(i).getRemotePath() +
                             " due to cancellation request");
         }
     }

+ 9 - 9
src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.java

@@ -710,12 +710,12 @@ public class FileDisplayActivity extends FileActivity
                                          boolean success) {
         FileFragment secondFragment = getSecondFragment();
         boolean waitedPreview = mWaitingToPreview != null
-                && mWaitingToPreview.getRemotePath().equals(downloadedRemotePath);
+            && mWaitingToPreview.getRemotePath().equals(downloadedRemotePath);
         if (secondFragment instanceof FileDetailFragment) {
             FileDetailFragment detailsFragment = (FileDetailFragment) secondFragment;
             OCFile fileInFragment = detailsFragment.getFile();
             if (fileInFragment != null &&
-                    !downloadedRemotePath.equals(fileInFragment.getRemotePath())) {
+                !downloadedRemotePath.equals(fileInFragment.getRemotePath())) {
                 // the user browsed to other file ; forget the automatic preview
                 mWaitingToPreview = null;
 
@@ -1326,9 +1326,9 @@ public class FileDisplayActivity extends FileActivity
 
                     } else {
                         OCFile currentFile = (getFile() == null) ? null :
-                                getStorageManager().getFileByPath(getFile().getRemotePath());
+                            getStorageManager().getFileByPath(getFile().getRemotePath());
                         OCFile currentDir = (getCurrentDir() == null) ? null :
-                                getStorageManager().getFileByPath(getCurrentDir().getRemotePath());
+                            getStorageManager().getFileByPath(getCurrentDir().getRemotePath());
 
                         if (currentDir == null) {
                             // current folder was removed from the server
@@ -1463,7 +1463,7 @@ public class FileDisplayActivity extends FileActivity
                 boolean sameAccount = getAccount() != null && accountName.equals(getAccount().name);
                 OCFile currentDir = getCurrentDir();
                 boolean isDescendant = currentDir != null && uploadedRemotePath != null &&
-                        uploadedRemotePath.startsWith(currentDir.getRemotePath());
+                    uploadedRemotePath.startsWith(currentDir.getRemotePath());
 
                 if (sameAccount && isDescendant) {
                     String linkedToRemotePath =
@@ -1588,13 +1588,13 @@ public class FileDisplayActivity extends FileActivity
             OCFile currentDir = getCurrentDir();
             return currentDir != null &&
                     downloadedRemotePath != null &&
-                    downloadedRemotePath.startsWith(currentDir.getRemotePath());
+                downloadedRemotePath.startsWith(currentDir.getRemotePath());
         }
 
         private boolean isAscendant(String linkedToRemotePath) {
             OCFile currentDir = getCurrentDir();
             return currentDir != null &&
-                    currentDir.getRemotePath().startsWith(linkedToRemotePath);
+                currentDir.getRemotePath().startsWith(linkedToRemotePath);
         }
 
         private boolean isSameAccount(Intent intent) {
@@ -2439,11 +2439,11 @@ public class FileDisplayActivity extends FileActivity
     public void cancelTransference(OCFile file) {
         getFileOperationsHelper().cancelTransference(file);
         if (mWaitingToPreview != null &&
-                mWaitingToPreview.getRemotePath().equals(file.getRemotePath())) {
+            mWaitingToPreview.getRemotePath().equals(file.getRemotePath())) {
             mWaitingToPreview = null;
         }
         if (mWaitingToSend != null &&
-                mWaitingToSend.getRemotePath().equals(file.getRemotePath())) {
+            mWaitingToSend.getRemotePath().equals(file.getRemotePath())) {
             mWaitingToSend = null;
         }
         onTransferStateChanged(file, false, false);

+ 2 - 3
src/main/java/com/owncloud/android/ui/activity/ReceiveExternalFilesActivity.java

@@ -38,7 +38,6 @@ import android.content.res.ColorStateList;
 import android.content.res.Resources.NotFoundException;
 import android.graphics.Color;
 import android.graphics.PorterDuff;
-import android.graphics.drawable.ColorDrawable;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Looper;
@@ -1106,9 +1105,9 @@ public class ReceiveExternalFilesActivity extends FileActivity
 
                     } else {
                         OCFile currentFile = (mFile == null) ? null :
-                                getStorageManager().getFileByPath(mFile.getRemotePath());
+                            getStorageManager().getFileByPath(mFile.getRemotePath());
                         OCFile currentDir = (getCurrentFolder() == null) ? null :
-                                getStorageManager().getFileByPath(getCurrentFolder().getRemotePath());
+                            getStorageManager().getFileByPath(getCurrentFolder().getRemotePath());
 
                         if (currentDir == null) {
                             // current folder was removed from the server

+ 5 - 2
src/main/java/com/owncloud/android/ui/adapter/OCFileListAdapter.java

@@ -258,7 +258,10 @@ public class OCFileListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
         int filesSize = mFiles.size();
         for (int i = 0; i < filesSize; i++) {
             if (mFiles.get(i).getRemoteId().equals(fileId)) {
-                mFiles.get(i).setEncrypted(encrypted);
+                OCFile file = mFiles.get(i);
+                file.setEncrypted(encrypted);
+                mStorageManager.saveFile(file);
+
                 break;
             }
         }
@@ -555,7 +558,7 @@ public class OCFileListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
             if (holder instanceof OCFileListGridItemViewHolder) {
                 OCFileListGridItemViewHolder gridItemViewHolder = (OCFileListGridItemViewHolder) holder;
 
-                gridItemViewHolder.fileName.setText(file.getFileName());
+                gridItemViewHolder.fileName.setText(file.getDecryptedFileName());
 
                 if (gridView && gridImage) {
                     gridItemViewHolder.fileName.setVisibility(View.GONE);

+ 0 - 3
src/main/java/com/owncloud/android/ui/dialog/ChooseRichDocumentsTemplateDialogFragment.java

@@ -22,7 +22,6 @@
 
 package com.owncloud.android.ui.dialog;
 
-import android.accounts.Account;
 import android.annotation.SuppressLint;
 import android.app.Activity;
 import android.app.Dialog;
@@ -48,9 +47,7 @@ import com.owncloud.android.datamodel.OCFile;
 import com.owncloud.android.datamodel.Template;
 import com.owncloud.android.files.CreateFileFromTemplateOperation;
 import com.owncloud.android.files.FetchTemplateOperation;
-import com.owncloud.android.lib.common.OwnCloudAccount;
 import com.owncloud.android.lib.common.OwnCloudClient;
-import com.owncloud.android.lib.common.OwnCloudClientManagerFactory;
 import com.owncloud.android.lib.common.operations.RemoteOperationResult;
 import com.owncloud.android.lib.common.utils.Log_OC;
 import com.owncloud.android.lib.resources.files.ReadFileRemoteOperation;

+ 2 - 2
src/main/java/com/owncloud/android/ui/dialog/CreateFolderDialogFragment.java

@@ -137,8 +137,8 @@ public class CreateFolderDialogFragment
                 return;
             }
 
-            String path = mParentFolder.getRemotePath() + newFolderName + OCFile.PATH_SEPARATOR;
-            ((ComponentsGetter) getActivity()).getFileOperationsHelper().createFolder(path, false);
+            String path = mParentFolder.getDecryptedRemotePath() + newFolderName + OCFile.PATH_SEPARATOR;
+            ((ComponentsGetter) getActivity()).getFileOperationsHelper().createFolder(path);
         }
     }
 }

+ 2 - 2
src/main/java/com/owncloud/android/ui/dialog/SetupEncryptionDialogFragment.java

@@ -236,7 +236,7 @@ public class SetupEncryptionDialogFragment extends DialogFragment {
         return dialog;
     }
 
-    private class DownloadKeysAsyncTask extends AsyncTask<Void, Void, String> {
+    public class DownloadKeysAsyncTask extends AsyncTask<Void, Void, String> {
         @Override
         protected void onPreExecute() {
             super.onPreExecute();
@@ -316,7 +316,7 @@ public class SetupEncryptionDialogFragment extends DialogFragment {
         }
     }
 
-    private class GenerateNewKeysAsyncTask extends AsyncTask<Void, Void, String> {
+    public class GenerateNewKeysAsyncTask extends AsyncTask<Void, Void, String> {
         @Override
         protected void onPreExecute() {
             super.onPreExecute();

+ 1 - 1
src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.java

@@ -607,7 +607,7 @@ public class FileDetailSharingFragment extends Fragment implements ShareeListAda
         } else {
             // Get public share
             publicShare = fileDataStorageManager.getFirstShareByPathAndType(file.getRemotePath(),
-                ShareType.PUBLIC_LINK, "");
+                                                                            ShareType.PUBLIC_LINK, "");
 
             // Update public share section
             updatePublicShareSection();

+ 8 - 4
src/main/java/com/owncloud/android/ui/fragment/OCFileListBottomSheetDialog.java

@@ -125,16 +125,20 @@ public class OCFileListBottomSheetDialog extends BottomSheetDialog {
                 ThemeUtils.getDefaultDisplayNameForRootFolder(getContext())));
 
         OCCapability capability = fileActivity.getCapabilities();
-        if (capability.getRichDocuments().isTrue() && capability.getRichDocumentsDirectEditing().isTrue() &&
+        if (capability.getRichDocuments().isTrue() &&
+            capability.getRichDocumentsDirectEditing().isTrue() &&
             android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP &&
-            capability.getRichDocumentsTemplatesAvailable().isTrue()) {
+            capability.getRichDocumentsTemplatesAvailable().isTrue() &&
+            !file.isEncrypted()) {
             templates.setVisibility(View.VISIBLE);
         }
 
         String json = new ArbitraryDataProvider(getContext().getContentResolver())
             .getValue(user.toPlatformAccount(), ArbitraryDataProvider.DIRECT_EDITING);
 
-        if (!json.isEmpty() && android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+        if (!json.isEmpty() &&
+            android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP &&
+            !file.isEncrypted()) {
             DirectEditing directEditing = new Gson().fromJson(json, DirectEditing.class);
 
             if (!directEditing.getCreators().isEmpty()) {
@@ -174,7 +178,7 @@ public class OCFileListBottomSheetDialog extends BottomSheetDialog {
             FileMenuFilter.isEditorAvailable(getContext().getContentResolver(),
                                              user,
                                              MimeTypeUtil.MIMETYPE_TEXT_MARKDOWN) &&
-            file != null) {
+            file != null && !file.isEncrypted()) {
             // richWorkspace
             // == "": no info set -> show button
             // == null: disabled on server side -> hide button

+ 3 - 2
src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java

@@ -1008,17 +1008,18 @@ public class OCFileListFragment extends ExtendedListFragment implements
                             .getCapability(account.getAccountName());
 
                         if (PreviewMediaFragment.canBePreviewed(file) && account.getServer().getVersion()
-                                .isMediaStreamingSupported()) {
+                            .isMediaStreamingSupported() && !file.isEncrypted()) {
                             // stream media preview on >= NC14
                             ((FileDisplayActivity) mContainerActivity).startMediaPreview(file, 0, true, true, true);
                         } else if (FileMenuFilter.isEditorAvailable(requireContext().getContentResolver(),
                                                                     accountManager.getUser(),
                                                                     file.getMimeType()) &&
+                            !file.isEncrypted() &&
                             android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                             mContainerActivity.getFileOperationsHelper().openFileWithTextEditor(file, getContext());
                         } else if (capability.getRichDocumentsMimeTypeList().contains(file.getMimeType()) &&
                             android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP &&
-                            capability.getRichDocumentsDirectEditing().isTrue()) {
+                            capability.getRichDocumentsDirectEditing().isTrue() && !file.isEncrypted()) {
                             mContainerActivity.getFileOperationsHelper().openFileAsRichDocument(file, getContext());
                         } else {
                             // automatic download, preview on finish

+ 2 - 2
src/main/java/com/owncloud/android/ui/fragment/SearchShareesFragment.java

@@ -160,8 +160,8 @@ public class SearchShareesFragment extends Fragment implements ShareUserListAdap
         // Get Users and Groups
         if (((FileActivity) mListener).getStorageManager() != null) {
             mShares = ((FileActivity) mListener).getStorageManager().getSharesWithForAFile(
-                    mFile.getRemotePath(),
-                    mAccount.name
+                mFile.getRemotePath(),
+                mAccount.name
             );
 
             // Update list of users/groups

+ 5 - 5
src/main/java/com/owncloud/android/ui/fragment/ShareFileFragment.java

@@ -606,8 +606,8 @@ public class ShareFileFragment extends Fragment implements ShareUserListAdapter.
         if (((FileActivity) mListener).getStorageManager() != null) {
             // Get Users and Groups
             mPrivateShares = ((FileActivity) mListener).getStorageManager().getSharesWithForAFile(
-                    mFile.getRemotePath(),
-                    mAccount.name
+                mFile.getRemotePath(),
+                mAccount.name
             );
 
             // Update list of users/groups
@@ -672,9 +672,9 @@ public class ShareFileFragment extends Fragment implements ShareUserListAdapter.
         } else if (((FileActivity) mListener).getStorageManager() != null) {
             // Get public share
             mPublicShare = ((FileActivity) mListener).getStorageManager().getFirstShareByPathAndType(
-                    mFile.getRemotePath(),
-                    ShareType.PUBLIC_LINK,
-                    ""
+                mFile.getRemotePath(),
+                ShareType.PUBLIC_LINK,
+                ""
             );
 
             // Update public share section

+ 6 - 7
src/main/java/com/owncloud/android/ui/helpers/FileOperationsHelper.java

@@ -227,7 +227,7 @@ public class FileOperationsHelper {
 
             // check for changed eTag
             CheckEtagRemoteOperation checkEtagOperation = new CheckEtagRemoteOperation(file.getRemotePath(),
-                file.getEtag());
+                                                                                       file.getEtag());
             RemoteOperationResult result = checkEtagOperation.execute(user.toPlatformAccount(), fileActivity);
 
             // eTag changed, sync file
@@ -766,7 +766,7 @@ public class FileOperationsHelper {
             sendIntent.setComponent(new ComponentName(packageName, activityName));
             sendIntent.putExtra(Intent.EXTRA_STREAM, Uri.parse("content://" +
                     context.getResources().getString(R.string.image_cache_provider_authority) +
-                    file.getRemotePath()));
+                                                                   file.getRemotePath()));
             sendIntent.putExtra(Intent.ACTION_SEND, true);      // Send Action
             sendIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
 
@@ -797,7 +797,7 @@ public class FileOperationsHelper {
                 } else {
                     uri = Uri.parse(UriUtils.URI_CONTENT_SCHEME +
                             context.getResources().getString(R.string.image_cache_provider_authority) +
-                            file.getRemotePath());
+                                        file.getRemotePath());
                 }
 
                 intent.setDataAndType(uri, file.getMimeType());
@@ -863,7 +863,7 @@ public class FileOperationsHelper {
     public void toggleEncryption(OCFile file, boolean shouldBeEncrypted) {
         if (file.isEncrypted() != shouldBeEncrypted) {
             EventBus.getDefault().post(new EncryptionEvent(file.getLocalId(), file.getRemoteId(), file.getRemotePath(),
-                    shouldBeEncrypted));
+                                                           shouldBeEncrypted));
         }
     }
 
@@ -894,7 +894,7 @@ public class FileOperationsHelper {
             Intent service = new Intent(fileActivity, OperationsService.class);
             service.setAction(OperationsService.ACTION_REMOVE);
             service.putExtra(OperationsService.EXTRA_ACCOUNT, fileActivity.getAccount());
-            service.putExtra(OperationsService.EXTRA_REMOTE_PATH, file.getRemotePath());
+            service.putExtra(OperationsService.EXTRA_FILE, file);
             service.putExtra(OperationsService.EXTRA_REMOVE_ONLY_LOCAL, onlyLocalCopy);
             service.putExtra(OperationsService.EXTRA_IN_BACKGROUND, inBackground);
             mWaitingForOpId = fileActivity.getOperationsServiceBinder().queueNewOperation(service);
@@ -906,13 +906,12 @@ public class FileOperationsHelper {
     }
 
 
-    public void createFolder(String remotePath, boolean createFullPath) {
+    public void createFolder(String remotePath) {
         // Create Folder
         Intent service = new Intent(fileActivity, OperationsService.class);
         service.setAction(OperationsService.ACTION_CREATE_FOLDER);
         service.putExtra(OperationsService.EXTRA_ACCOUNT, fileActivity.getAccount());
         service.putExtra(OperationsService.EXTRA_REMOTE_PATH, remotePath);
-        service.putExtra(OperationsService.EXTRA_CREATE_FULL_PATH, createFullPath);
         mWaitingForOpId = fileActivity.getOperationsServiceBinder().queueNewOperation(service);
 
         fileActivity.showLoadingDialog(fileActivity.getString(R.string.wait_a_moment));

+ 0 - 1
src/main/java/com/owncloud/android/ui/trashbin/TrashbinActivity.java

@@ -53,7 +53,6 @@ import java.util.List;
 
 import javax.inject.Inject;
 
-import androidx.appcompat.widget.Toolbar;
 import androidx.fragment.app.FragmentManager;
 import androidx.fragment.app.FragmentTransaction;
 import androidx.recyclerview.widget.LinearLayoutManager;

+ 99 - 2
src/main/java/com/owncloud/android/utils/EncryptionUtils.java

@@ -24,7 +24,9 @@ package com.owncloud.android.utils;
 import android.accounts.Account;
 import android.content.Context;
 import android.os.Build;
+import android.text.TextUtils;
 import android.util.Base64;
+import android.util.Pair;
 
 import com.google.gson.Gson;
 import com.google.gson.reflect.TypeToken;
@@ -36,8 +38,14 @@ import com.owncloud.android.lib.common.OwnCloudClient;
 import com.owncloud.android.lib.common.operations.RemoteOperationResult;
 import com.owncloud.android.lib.common.utils.Log_OC;
 import com.owncloud.android.lib.resources.e2ee.GetMetadataRemoteOperation;
+import com.owncloud.android.lib.resources.e2ee.LockFileRemoteOperation;
+import com.owncloud.android.lib.resources.e2ee.StoreMetadataRemoteOperation;
+import com.owncloud.android.lib.resources.e2ee.UnlockFileRemoteOperation;
+import com.owncloud.android.lib.resources.e2ee.UpdateMetadataRemoteOperation;
+import com.owncloud.android.operations.UploadException;
 
 import org.apache.commons.codec.binary.Hex;
+import org.apache.commons.httpclient.HttpStatus;
 
 import java.io.BufferedReader;
 import java.io.ByteArrayInputStream;
@@ -97,9 +105,9 @@ public final class EncryptionUtils {
     public static final String MNEMONIC = "MNEMONIC";
     public static final int ivLength = 16;
     public static final int saltLength = 40;
+    public static final String ivDelimiter = "|"; // not base64 encoded
 
     private static final String HASH_DELIMITER = "$";
-    private static final String ivDelimiter = "fA=="; // "|" base64 encoded
     private static final int iterationCount = 1024;
     private static final int keyStrength = 256;
     private static final String AES_CIPHER = "AES/GCM/NoPadding";
@@ -539,7 +547,7 @@ public final class EncryptionUtils {
             IllegalBlockSizeException, InvalidKeySpecException, InvalidAlgorithmParameterException {
 
         // split up iv, salt
-        String[] strings = privateKey.split(ivDelimiter);
+        String[] strings = privateKey.split("\\" + ivDelimiter);
         String realPrivateKey = strings[0];
         byte[] iv = decodeStringToBase64Bytes(strings[1]);
         byte[] salt = decodeStringToBase64Bytes(strings[2]);
@@ -704,4 +712,93 @@ public final class EncryptionUtils {
 
         return hashWithSalt.equals(newHash);
     }
+
+    public static String lockFolder(OCFile parentFile, OwnCloudClient client) throws UploadException {
+        // Lock folder
+        LockFileRemoteOperation lockFileOperation = new LockFileRemoteOperation(parentFile.getLocalId());
+        RemoteOperationResult lockFileOperationResult = lockFileOperation.execute(client);
+
+        if (lockFileOperationResult.isSuccess() &&
+            !TextUtils.isEmpty((String) lockFileOperationResult.getData().get(0))) {
+            return (String) lockFileOperationResult.getData().get(0);
+        } else if (lockFileOperationResult.getHttpCode() == HttpStatus.SC_FORBIDDEN) {
+            throw new UploadException("Forbidden! Please try again later.)");
+        } else {
+            throw new UploadException("Could not lock folder");
+        }
+    }
+
+    /**
+     * @param parentFile file metadata should be retrieved for
+     * @return Pair: boolean: true: metadata already exists, false: metadata new created
+     */
+    @RequiresApi(api = Build.VERSION_CODES.KITKAT)
+    public static Pair<Boolean, DecryptedFolderMetadata> retrieveMetadata(OCFile parentFile,
+                                                                          OwnCloudClient client,
+                                                                          String privateKey,
+                                                                          String publicKey) throws UploadException,
+        InvalidAlgorithmParameterException, NoSuchAlgorithmException, NoSuchPaddingException, BadPaddingException,
+        IllegalBlockSizeException, InvalidKeyException, InvalidKeySpecException, CertificateException {
+        GetMetadataRemoteOperation getMetadataOperation = new GetMetadataRemoteOperation(parentFile.getLocalId());
+        RemoteOperationResult getMetadataOperationResult = getMetadataOperation.execute(client);
+
+        DecryptedFolderMetadata metadata;
+
+        if (getMetadataOperationResult.isSuccess()) {
+            // decrypt metadata
+            String serializedEncryptedMetadata = (String) getMetadataOperationResult.getData().get(0);
+
+
+            EncryptedFolderMetadata encryptedFolderMetadata = EncryptionUtils.deserializeJSON(
+                serializedEncryptedMetadata, new TypeToken<EncryptedFolderMetadata>() {
+                });
+
+            return new Pair<>(Boolean.TRUE, EncryptionUtils.decryptFolderMetaData(encryptedFolderMetadata, privateKey));
+
+        } else if (getMetadataOperationResult.getHttpCode() == HttpStatus.SC_NOT_FOUND) {
+            // new metadata
+            metadata = new DecryptedFolderMetadata();
+            metadata.setMetadata(new DecryptedFolderMetadata.Metadata());
+            metadata.getMetadata().setMetadataKeys(new HashMap<>());
+            String metadataKey = EncryptionUtils.encodeBytesToBase64String(EncryptionUtils.generateKey());
+            String encryptedMetadataKey = EncryptionUtils.encryptStringAsymmetric(metadataKey, publicKey);
+            metadata.getMetadata().getMetadataKeys().put(0, encryptedMetadataKey);
+
+            return new Pair<>(Boolean.FALSE, metadata);
+        } else {
+            // TODO error
+            throw new UploadException("something wrong");
+        }
+    }
+
+    public static void uploadMetadata(OCFile parentFile,
+                                      String serializedFolderMetadata,
+                                      String token,
+                                      OwnCloudClient client,
+                                      boolean metadataExists) throws UploadException {
+        RemoteOperationResult uploadMetadataOperationResult;
+        if (metadataExists) {
+            // update metadata
+            UpdateMetadataRemoteOperation storeMetadataOperation = new UpdateMetadataRemoteOperation(
+                parentFile.getLocalId(), serializedFolderMetadata, token);
+            uploadMetadataOperationResult = storeMetadataOperation.execute(client);
+        } else {
+            // store metadata
+            StoreMetadataRemoteOperation storeMetadataOperation = new StoreMetadataRemoteOperation(
+                parentFile.getLocalId(), serializedFolderMetadata);
+            uploadMetadataOperationResult = storeMetadataOperation.execute(client);
+        }
+
+        if (!uploadMetadataOperationResult.isSuccess()) {
+            throw new UploadException("Storing/updating metadata was not successful");
+        }
+    }
+
+    public static RemoteOperationResult unlockFolder(OCFile parentFolder, OwnCloudClient client, String token) {
+        if (token != null) {
+            return new UnlockFileRemoteOperation(parentFolder.getLocalId(), token).execute(client);
+        } else {
+            return new RemoteOperationResult(new Exception("No token available"));
+        }
+    }
 }

+ 2 - 1
src/main/java/com/owncloud/android/utils/FileStorageUtils.java

@@ -201,6 +201,7 @@ public final class FileStorageUtils {
      */
     public static OCFile fillOCFile(RemoteFile remote) {
         OCFile file = new OCFile(remote.getRemotePath());
+        file.setDecryptedRemotePath(remote.getRemotePath());
         file.setCreationTimestamp(remote.getCreationTimestamp());
         if (MimeType.DIRECTORY.equalsIgnoreCase(remote.getMimeType())) {
             file.setFileLength(remote.getSize());
@@ -449,7 +450,7 @@ public final class FileStorageUtils {
             return true;
         }
 
-        while (!OCFile.ROOT_PATH.equals(file.getRemotePath())) {
+        while (!OCFile.ROOT_PATH.equals(file.getDecryptedRemotePath())) {
             if (file.isEncrypted()) {
                 return true;
             }

+ 1 - 1
src/main/java/com/owncloud/android/utils/GetShareWithUsersAsyncTask.java

@@ -93,4 +93,4 @@ public class GetShareWithUsersAsyncTask extends AsyncTask<Object, Void, Pair<Rem
         }
     }
 
-}
+}

+ 2 - 0
src/main/java/com/owncloud/android/utils/MimeType.java

@@ -22,9 +22,11 @@ package com.owncloud.android.utils;
  */
 public final class MimeType {
     public static final String DIRECTORY = "DIR";
+    public static final String WEBDAV_FOLDER = "httpd/unix-directory";
     public static final String JPEG = "image/jpeg";
     public static final String TIFF = "image/tiff";
     public static final String TEXT_PLAIN = "text/plain";
+    public static final String FILE = "application/octet-stream";
 
     private MimeType() {
         // No instance

+ 2 - 2
src/main/java/com/owncloud/android/utils/MimeTypeUtil.java

@@ -301,7 +301,7 @@ public final class MimeTypeUtil {
      */
     public static boolean isImage(ServerFileInterface file) {
         return MimeTypeUtil.isImage(file.getMimeType())
-                || MimeTypeUtil.isImage(getMimeTypeFromPath(file.getRemotePath()));
+            || MimeTypeUtil.isImage(getMimeTypeFromPath(file.getRemotePath()));
     }
 
     /**
@@ -310,7 +310,7 @@ public final class MimeTypeUtil {
      */
     public static boolean isText(OCFile file) {
         return MimeTypeUtil.isText(file.getMimeType())
-                || MimeTypeUtil.isText(getMimeTypeFromPath(file.getRemotePath()));
+            || MimeTypeUtil.isText(getMimeTypeFromPath(file.getRemotePath()));
     }
 
     /**