Bladeren bron

First release of E2E

Jan-Christoph Borchardt 7 jaren geleden
bovenliggende
commit
6caf0834ce

+ 1 - 1
build.gradle

@@ -195,7 +195,7 @@ dependencies {
     /// dependencies for app building
     /// dependencies for app building
     implementation 'com.android.support:multidex:1.0.2'
     implementation 'com.android.support:multidex:1.0.2'
     implementation 'com.github.nextcloud:android-library:1.0.33'
     implementation 'com.github.nextcloud:android-library:1.0.33'
-    versionDevImplementation 'com.github.nextcloud:android-library:e2e-SNAPSHOT' // use always latest master
+    versionDevImplementation 'com.github.nextcloud:android-library:master-SNAPSHOT' // use always latest master
     implementation "com.android.support:support-v4:${supportLibraryVersion}"
     implementation "com.android.support:support-v4:${supportLibraryVersion}"
     implementation "com.android.support:design:${supportLibraryVersion}"
     implementation "com.android.support:design:${supportLibraryVersion}"
     implementation 'com.jakewharton:disklrucache:2.0.2'
     implementation 'com.jakewharton:disklrucache:2.0.2'

+ 2 - 2
settings.gradle

@@ -1,2 +1,2 @@
-//include ':'
-include 'nextcloud-android-library'
+include ':'
+//include 'nextcloud-android-library'

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

@@ -222,7 +222,7 @@ public class EncryptionTestIT {
         Set<String> keys = new HashSet<>();
         Set<String> keys = new HashSet<>();
 
 
         for (int i = 0; i < 50; i++) {
         for (int i = 0; i < 50; i++) {
-            assertTrue(keys.add(EncryptionUtils.encodeBytesToBase64String(EncryptionUtils.generateKey())));
+            assertTrue(keys.add(encodeBytesToBase64String(generateKey())));
         }
         }
     }
     }
 
 
@@ -234,7 +234,7 @@ public class EncryptionTestIT {
         Set<String> ivs = new HashSet<>();
         Set<String> ivs = new HashSet<>();
 
 
         for (int i = 0; i < 50; i++) {
         for (int i = 0; i < 50; i++) {
-            assertTrue(ivs.add(EncryptionUtils.encodeBytesToBase64String(EncryptionUtils.generateIV())));
+            assertTrue(ivs.add(encodeBytesToBase64String(generateIV())));
         }
         }
     }
     }
 
 
@@ -255,9 +255,9 @@ public class EncryptionTestIT {
     }
     }
 
 
     private DecryptedFolderMetadata generateFolderMetadata() throws Exception {
     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(EncryptionUtils.generateKey());
+        String metadataKey1 = encodeBytesToBase64String(EncryptionUtils.generateKey());
+        String metadataKey2 = encodeBytesToBase64String(EncryptionUtils.generateKey());
         HashMap<Integer, String> metadataKeys = new HashMap<>();
         HashMap<Integer, String> metadataKeys = new HashMap<>();
         metadataKeys.put(0, EncryptionUtils.encryptStringAsymmetric(metadataKey0, cert));
         metadataKeys.put(0, EncryptionUtils.encryptStringAsymmetric(metadataKey0, cert));
         metadataKeys.put(1, EncryptionUtils.encryptStringAsymmetric(metadataKey1, cert));
         metadataKeys.put(1, EncryptionUtils.encryptStringAsymmetric(metadataKey1, cert));
@@ -320,7 +320,7 @@ public class EncryptionTestIT {
         fileOutputStream.write(encryptedFile.encryptedBytes);
         fileOutputStream.write(encryptedFile.encryptedBytes);
         fileOutputStream.close();
         fileOutputStream.close();
 
 
-        byte[] authenticationTag = EncryptionUtils.decodeStringToBase64Bytes(encryptedFile.authenticationTag);
+        byte[] authenticationTag = decodeStringToBase64Bytes(encryptedFile.authenticationTag);
 
 
         // verify authentication tag
         // verify authentication tag
         assertTrue(Arrays.equals(expectedAuthTag, authenticationTag));
         assertTrue(Arrays.equals(expectedAuthTag, authenticationTag));

+ 3 - 1
src/gplay/AndroidManifest.xml

@@ -18,7 +18,9 @@
 -->
 -->
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           xmlns:tools="http://schemas.android.com/tools"
           xmlns:tools="http://schemas.android.com/tools"
-          package="com.owncloud.android">
+          package="com.owncloud.android"
+          android:versionCode="20000052"
+          android:versionName="2.0.0-e2e-02">
 
 
     <application
     <application
         android:name=".MainApp"
         android:name=".MainApp"

+ 3 - 1
src/main/AndroidManifest.xml

@@ -19,7 +19,9 @@
   along with this program.  If not, see <http://www.gnu.org/licenses/>.
   along with this program.  If not, see <http://www.gnu.org/licenses/>.
 -->
 -->
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="com.owncloud.android">
+          package="com.owncloud.android"
+          android:versionCode="20000052"
+          android:versionName="2.0.0-e2e-02">
 
 
     <!-- GET_ACCOUNTS is needed for API <= 22.
     <!-- GET_ACCOUNTS is needed for API <= 22.
         For API >= 23 results in the addition of CONTACTS group to the list of permissions that may be
         For API >= 23 results in the addition of CONTACTS group to the list of permissions that may be

+ 3 - 0
src/main/java/com/owncloud/android/MainApp.java

@@ -64,6 +64,7 @@ import com.owncloud.android.utils.AnalyticsUtils;
 import com.owncloud.android.utils.FilesSyncHelper;
 import com.owncloud.android.utils.FilesSyncHelper;
 import com.owncloud.android.utils.PermissionUtil;
 import com.owncloud.android.utils.PermissionUtil;
 import com.owncloud.android.utils.ReceiversHelper;
 import com.owncloud.android.utils.ReceiversHelper;
+import com.owncloud.android.utils.EncryptionUtils;
 
 
 import java.lang.reflect.Method;
 import java.lang.reflect.Method;
 import java.util.ArrayList;
 import java.util.ArrayList;
@@ -71,6 +72,8 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.List;
 import java.util.Map;
 import java.util.Map;
 
 
+import javax.crypto.Cipher;
+
 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
 
 
 
 

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

@@ -188,7 +188,7 @@ public class FileDownloader extends Service
             AbstractList<String> requestedDownloads = new Vector<String>();
             AbstractList<String> requestedDownloads = new Vector<String>();
             try {
             try {
                 DownloadFileOperation newDownload = new DownloadFileOperation(account, file, behaviour, activityName,
                 DownloadFileOperation newDownload = new DownloadFileOperation(account, file, behaviour, activityName,
-                        packageName);
+                        packageName, getBaseContext());
                 newDownload.addDatatransferProgressListener(this);
                 newDownload.addDatatransferProgressListener(this);
                 newDownload.addDatatransferProgressListener((FileDownloaderBinder) mBinder);
                 newDownload.addDatatransferProgressListener((FileDownloaderBinder) mBinder);
                 Pair<String, String> putResult = mPendingDownloads.putIfAbsent(
                 Pair<String, String> putResult = mPendingDownloads.putIfAbsent(

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

@@ -66,8 +66,8 @@ public class DownloadFileOperation extends RemoteOperation {
     private String mPackageName;
     private String mPackageName;
 
 
 
 
-    public DownloadFileOperation(Account account, OCFile file, String behaviour, String activityName,
-                                 String packageName) {
+    public DownloadFileOperation(Account account, OCFile file, String behaviour, String activityName, 
+                                 String packageName, Context context) {
         if (account == null) {
         if (account == null) {
             throw new IllegalArgumentException("Illegal null account in DownloadFileOperation " +
             throw new IllegalArgumentException("Illegal null account in DownloadFileOperation " +
                     "creation");
                     "creation");
@@ -82,6 +82,7 @@ public class DownloadFileOperation extends RemoteOperation {
         mBehaviour = behaviour;
         mBehaviour = behaviour;
         mActivityName = activityName;
         mActivityName = activityName;
         mPackageName = packageName;
         mPackageName = packageName;
+        mContext = context;
     }
     }
 
 
 
 

+ 880 - 0
src/main/java/com/owncloud/android/operations/UploadEncryptedFileOperation.java

@@ -0,0 +1,880 @@
+/*
+ * Nextcloud Android client application
+ *
+ * @author Tobias Kaminsky
+ * Copyright (C) 2017 Tobias Kaminsky
+ * Copyright (C) 2017 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 <http://www.gnu.org/licenses/>.
+ */
+
+package com.owncloud.android.operations;
+
+import android.accounts.Account;
+import android.content.Context;
+import android.os.Build;
+import android.support.annotation.RequiresApi;
+
+import com.google.gson.reflect.TypeToken;
+import com.owncloud.android.datamodel.ArbitraryDataProvider;
+import com.owncloud.android.datamodel.DecryptedFolderMetadata;
+import com.owncloud.android.datamodel.EncryptedFolderMetadata;
+import com.owncloud.android.datamodel.FileDataStorageManager;
+import com.owncloud.android.datamodel.OCFile;
+import com.owncloud.android.files.services.FileUploader;
+import com.owncloud.android.lib.common.OwnCloudClient;
+import com.owncloud.android.lib.common.network.OnDatatransferProgressListener;
+import com.owncloud.android.lib.common.network.ProgressiveDataTransferer;
+import com.owncloud.android.lib.common.operations.OperationCancelledException;
+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.files.ChunkedUploadRemoteFileOperation;
+import com.owncloud.android.lib.resources.files.ExistenceCheckRemoteOperation;
+import com.owncloud.android.lib.resources.files.GetMetadataOperation;
+import com.owncloud.android.lib.resources.files.LockFileOperation;
+import com.owncloud.android.lib.resources.files.ReadRemoteFileOperation;
+import com.owncloud.android.lib.resources.files.RemoteFile;
+import com.owncloud.android.lib.resources.files.StoreMetadataOperation;
+import com.owncloud.android.lib.resources.files.UnlockFileOperation;
+import com.owncloud.android.lib.resources.files.UpdateMetadataOperation;
+import com.owncloud.android.lib.resources.files.UploadRemoteFileOperation;
+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 org.apache.commons.httpclient.HttpStatus;
+import org.apache.commons.httpclient.methods.RequestEntity;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+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.Iterator;
+import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import static com.owncloud.android.utils.EncryptionUtils.encodeStringToBase64Bytes;
+
+
+/**
+ * Operation performing the update in the ownCloud server
+ * of a file that was modified locally.
+ */
+@RequiresApi(api = Build.VERSION_CODES.KITKAT)
+public class UploadEncryptedFileOperation extends SyncOperation {
+
+    private static final String TAG = UploadEncryptedFileOperation.class.getSimpleName();
+
+    public static final int CREATED_BY_USER = 0;
+    public static final int CREATED_AS_INSTANT_PICTURE = 1;
+    public static final int CREATED_AS_INSTANT_VIDEO = 2;
+    private OCFile parentFile;
+
+    /**
+     * OCFile which is to be uploaded.
+     */
+    private OCFile ocFile;
+    private int localBehaviour = FileUploader.LOCAL_BEHAVIOUR_COPY;
+    private final String originalStoragePath;
+    private boolean chunked = false;
+    private boolean mRemoteFolderToBeCreated = false;
+    private int mCreatedBy = CREATED_BY_USER;
+
+    private long mOCUploadId = -1;
+    /**
+     * Local path to file which is to be uploaded (before any possible renaming or moving).
+     */
+    private Set<OnDatatransferProgressListener> mDataTransferListeners = new HashSet<OnDatatransferProgressListener>();
+
+    private final AtomicBoolean mCancellationRequested = new AtomicBoolean(false);
+    private final AtomicBoolean mUploadStarted = new AtomicBoolean(false);
+
+    private Context context;
+
+    private UploadRemoteFileOperation mUploadOperation;
+
+    protected RequestEntity mEntity = null;
+
+    private Account account;
+    private ArbitraryDataProvider arbitraryDataProvider;
+
+    public UploadEncryptedFileOperation(OCFile parent, UploadFileOperation uploadFileOperation) {
+        parentFile = parent;
+        ocFile = uploadFileOperation.getFile();
+        account = uploadFileOperation.getAccount();
+        chunked = uploadFileOperation.isChunkedUploadSupported();
+        context = uploadFileOperation.getContext();
+        localBehaviour = uploadFileOperation.getLocalBehaviour();
+        originalStoragePath = uploadFileOperation.getStoragePath();
+
+        arbitraryDataProvider = new ArbitraryDataProvider(context.getContentResolver());
+    }
+
+    public Account getAccount() {
+        return account;
+    }
+
+    public String getFileName() {
+        return (ocFile != null) ? ocFile.getFileName() : null;
+    }
+
+    public OCFile getFile() {
+        return ocFile;
+    }
+
+    public String getStoragePath() {
+        return ocFile.getStoragePath();
+    }
+
+    public String getRemotePath() {
+        return ocFile.getParentRemotePath() + ocFile.getEncryptedFileName();
+    }
+
+    public String getMimeType() {
+        return ocFile.getMimetype();
+    }
+
+    public void setRemoteFolderToBeCreated() {
+        mRemoteFolderToBeCreated = true;
+    }
+
+    public void setCreatedBy(int createdBy) {
+        mCreatedBy = createdBy;
+        if (createdBy < CREATED_BY_USER || CREATED_AS_INSTANT_VIDEO < createdBy) {
+            mCreatedBy = CREATED_BY_USER;
+        }
+    }
+
+    public int getCreatedBy() {
+        return mCreatedBy;
+    }
+
+    public boolean isInstantPicture() {
+        return mCreatedBy == CREATED_AS_INSTANT_PICTURE;
+    }
+
+    public boolean isInstantVideo() {
+        return mCreatedBy == CREATED_AS_INSTANT_VIDEO;
+    }
+
+    public void setOCUploadId(long id) {
+        mOCUploadId = id;
+    }
+
+    public long getOCUploadId() {
+        return mOCUploadId;
+    }
+
+    public Set<OnDatatransferProgressListener> getDataTransferListeners() {
+        return mDataTransferListeners;
+    }
+
+    public void addDatatransferProgressListener(OnDatatransferProgressListener listener) {
+        synchronized (mDataTransferListeners) {
+            mDataTransferListeners.add(listener);
+        }
+        if (mEntity != null) {
+            ((ProgressiveDataTransferer) mEntity).addDatatransferProgressListener(listener);
+        }
+        if (mUploadOperation != null) {
+            mUploadOperation.addDatatransferProgressListener(listener);
+        }
+    }
+
+    public void removeDatatransferProgressListener(OnDatatransferProgressListener listener) {
+        synchronized (mDataTransferListeners) {
+            mDataTransferListeners.remove(listener);
+        }
+        if (mEntity != null) {
+            ((ProgressiveDataTransferer) mEntity).removeDatatransferProgressListener(listener);
+        }
+        if (mUploadOperation != null) {
+            mUploadOperation.removeDatatransferProgressListener(listener);
+        }
+    }
+
+    @Override
+    protected RemoteOperationResult run(OwnCloudClient client) {
+        RemoteOperationResult result = null;
+        boolean metadataExists = false;
+        String token = null;
+        mCancellationRequested.set(false);
+        mUploadStarted.set(true);
+
+        File temporalFile = null;
+        File originalFile = new File(ocFile.getStoragePath());
+        File expectedFile = null;
+        FileLock fileLock = null;
+
+        String privateKey = arbitraryDataProvider.getValue(account.name, EncryptionUtils.PRIVATE_KEY);
+        String publicKey = arbitraryDataProvider.getValue(account.name, EncryptionUtils.PUBLIC_KEY);
+
+        try {
+
+            /// check if the file continues existing before schedule the operation
+            if (!originalFile.exists()) {
+                Log_OC.d(TAG, ocFile.getStoragePath() + " not exists anymore");
+                throw new FileNotFoundException();
+            }
+
+            /// check the existence of the parent folder for the file to upload
+            String remoteParentPath = new File(getRemotePath()).getParent();
+            remoteParentPath = remoteParentPath.endsWith(OCFile.PATH_SEPARATOR) ?
+                    remoteParentPath : remoteParentPath + OCFile.PATH_SEPARATOR;
+            result = grantFolderExistence(remoteParentPath, client);
+
+            if (!result.isSuccess()) {
+                return result;
+            }
+
+            // TODO automatic rename? UploadFileOperation:365
+
+            /// set parent local id in uploading file
+//            OCFile parent = getStorageManager().getFileByPath(remoteParentPath);
+//            ocFile.setParentId(parent.getFileId());
+
+
+//        if (mCancellationRequested.get()) {
+//            throw new OperationCancelledException();
+//        }
+
+            // Get the last modification date of the file from the file system
+            Long timeStampLong = originalFile.lastModified() / 1000;
+            String timeStamp = timeStampLong.toString();
+
+            // Lock folder
+            LockFileOperation lockFileOperation = new LockFileOperation(parentFile.getLocalId());
+            RemoteOperationResult lockFileOperationResult = lockFileOperation.execute(client);
+
+
+            if (lockFileOperationResult.isSuccess()) {
+                token = (String) lockFileOperationResult.getData().get(0);
+            } else if (lockFileOperationResult.getHttpCode() == HttpStatus.SC_FORBIDDEN) {
+                throw new Exception("Forbidden! Please try again later.)");
+            } else {
+                throw new Exception("Unknown error!");
+            }
+
+            // Update metadata
+            GetMetadataOperation getMetadataOperation = new GetMetadataOperation(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>() {
+                        });
+
+                metadata = EncryptionUtils.decryptFolderMetaData(encryptedFolderMetadata, privateKey);
+
+            } else if (getMetadataOperationResult.getHttpCode() == HttpStatus.SC_NOT_FOUND) {
+                // new metadata
+                metadata = new DecryptedFolderMetadata();
+                metadata.metadata = new DecryptedFolderMetadata.Metadata();
+                metadata.metadata.metadataKeys = new HashMap<>();
+                String metadataKey = EncryptionUtils.encodeBytesToBase64String(EncryptionUtils.generateKey());
+                String encryptedMetadataKey = EncryptionUtils.encryptStringAsymmetric(metadataKey, publicKey);
+                metadata.metadata.metadataKeys.put(0, encryptedMetadataKey);
+            } else {
+                // TODO error
+                throw new Exception("something wrong");
+            }
+
+            // Key
+            byte[] key = null;
+
+            try {
+                // TODO change key if file has changed, e.g. when file is updated
+                key = encodeStringToBase64Bytes(metadata.files.get(ocFile.getFileName()).encrypted.key);
+            } catch (Exception e) {
+                // no key found
+            }
+
+            if (key == null || key.length == 0) {
+                key = EncryptionUtils.generateKey();
+            }
+
+            // IV
+            byte[] iv = null;
+
+            try {
+                iv = encodeStringToBase64Bytes(metadata.files.get(ocFile.getFileName()).initializationVector);
+            } catch (Exception e) {
+                // no iv found
+            }
+
+            if (iv == null || iv.length == 0) {
+                iv = EncryptionUtils.generateIV();
+            }
+
+
+            EncryptionUtils.EncryptedFile encryptedFile = EncryptionUtils.encryptFile(ocFile, key, iv);
+
+            // new random file name, check if it exists in metadata
+            String encryptedFileName = UUID.randomUUID().toString().replaceAll("-", "");
+
+            while (metadata.files.get(encryptedFileName) != null) {
+                encryptedFileName = UUID.randomUUID().toString().replaceAll("-", "");
+            }
+
+            ocFile.setEncryptedFileName(encryptedFileName);
+
+            File encryptedTempFile = File.createTempFile("encFile", encryptedFileName);
+            FileOutputStream fileOutputStream = new FileOutputStream(encryptedTempFile);
+            fileOutputStream.write(encryptedFile.encryptedBytes);
+            fileOutputStream.close();
+
+            /// perform the upload
+            if (chunked &&
+                    (new File(ocFile.getStoragePath())).length() >
+                            ChunkedUploadRemoteFileOperation.CHUNK_SIZE) {
+                mUploadOperation = new ChunkedUploadRemoteFileOperation(context, encryptedTempFile.getAbsolutePath(),
+                        ocFile.getParentRemotePath() + encryptedFileName, ocFile.getMimetype(),
+                        ocFile.getEtagInConflict(), timeStamp);
+            } else {
+                mUploadOperation = new UploadRemoteFileOperation(encryptedTempFile.getAbsolutePath(),
+                        ocFile.getParentRemotePath() + encryptedFileName, ocFile.getMimetype(),
+                        ocFile.getEtagInConflict(), timeStamp);
+            }
+
+            Iterator<OnDatatransferProgressListener> listener = mDataTransferListeners.iterator();
+            while (listener.hasNext()) {
+                mUploadOperation.addDatatransferProgressListener(listener.next());
+            }
+
+            if (mCancellationRequested.get()) {
+                throw new OperationCancelledException();
+            }
+
+//            FileChannel channel = null;
+//            try {
+//                channel = new RandomAccessFile(ocFile.getStoragePath(), "rw").getChannel();
+//                fileLock = channel.tryLock();
+//            } catch (FileNotFoundException e) {
+//                if (temporalFile == null) {
+//                    String temporalPath = FileStorageUtils.getTemporalPath(account.name) + ocFile.getRemotePath();
+//                    ocFile.setStoragePath(temporalPath);
+//                    temporalFile = new File(temporalPath);
+//
+//                    result = copy(originalFile, temporalFile);
+//
+//                    if (result != null) {
+//                        return result;
+//                    } else {
+//                        if (temporalFile.length() == originalFile.length()) {
+//                            channel = new RandomAccessFile(temporalFile.getAbsolutePath(), "rw").getChannel();
+//                            fileLock = channel.tryLock();
+//                        } else {
+//                            while (temporalFile.length() != originalFile.length()) {
+//                                Files.deleteIfExists(Paths.get(temporalPath));
+//                                result = copy(originalFile, temporalFile);
+//
+//                                if (result != null) {
+//                                    return result;
+//                                } else {
+//                                    channel = new RandomAccessFile(temporalFile.getAbsolutePath(), "rw").
+//                                            getChannel();
+//                                    fileLock = channel.tryLock();
+//                                }
+//                            }
+//                        }
+//                    }
+//                } else {
+//                    channel = new RandomAccessFile(temporalFile.getAbsolutePath(), "rw").getChannel();
+//                    fileLock = channel.tryLock();
+//                }
+//            }
+
+            result = mUploadOperation.execute(client);
+
+            /// move local temporal file or original file to its corresponding
+            // location in the ownCloud local folder
+            if (!result.isSuccess() && result.getHttpCode() == HttpStatus.SC_PRECONDITION_FAILED) {
+                result = new RemoteOperationResult(ResultCode.SYNC_CONFLICT);
+            }
+
+            if (result.isSuccess()) {
+                // upload metadata
+                DecryptedFolderMetadata.DecryptedFile decryptedFile = new DecryptedFolderMetadata.DecryptedFile();
+                DecryptedFolderMetadata.Data data = new DecryptedFolderMetadata.Data();
+                data.filename = ocFile.getFileName();
+                data.mimetype = ocFile.getMimetype();
+                data.key = EncryptionUtils.encodeBytesToBase64String(key);
+
+                decryptedFile.encrypted = data;
+                decryptedFile.initializationVector = EncryptionUtils.encodeBytesToBase64String(iv);
+                decryptedFile.authenticationTag = encryptedFile.authenticationTag;
+
+                metadata.files.put(encryptedFileName, decryptedFile);
+
+                EncryptedFolderMetadata encryptedFolderMetadata = EncryptionUtils.encryptFolderMetadata(metadata,
+                        privateKey);
+                String serializedFolderMetadata = EncryptionUtils.serializeJSON(encryptedFolderMetadata);
+
+                // upload metadata
+                RemoteOperationResult uploadMetadataOperationResult;
+                if (metadataExists) {
+                    // update metadata
+                    UpdateMetadataOperation storeMetadataOperation = new UpdateMetadataOperation(parentFile.getLocalId(),
+                            serializedFolderMetadata, token);
+                    uploadMetadataOperationResult = storeMetadataOperation.execute(client);
+                } else {
+                    // store metadata
+                    StoreMetadataOperation storeMetadataOperation = new StoreMetadataOperation(parentFile.getLocalId(),
+                            serializedFolderMetadata);
+                    uploadMetadataOperationResult = storeMetadataOperation.execute(client);
+                }
+
+                if (!uploadMetadataOperationResult.isSuccess()) {
+                    throw new Exception();
+                }
+            }
+        } catch (FileNotFoundException e) {
+            Log_OC.d(TAG, ocFile.getStoragePath() + " not exists anymore");
+            result = new RemoteOperationResult(ResultCode.LOCAL_FILE_NOT_FOUND);
+        } catch (OverlappingFileLockException e) {
+            Log_OC.d(TAG, "Overlapping file lock exception");
+            result = new RemoteOperationResult(ResultCode.LOCK_FAILED);
+        } catch (Exception e) {
+            result = new RemoteOperationResult(e);
+
+        } finally {
+            mUploadStarted.set(false);
+
+            // unlock file
+            if (token != null) {
+                UnlockFileOperation unlockFileOperation = new UnlockFileOperation(parentFile.getLocalId(), token);
+                RemoteOperationResult unlockFileOperationResult = unlockFileOperation.execute(client);
+
+                if (!unlockFileOperationResult.isSuccess()) {
+                    Log_OC.e(TAG, "Failed to unlock " + parentFile.getLocalId());
+                }
+            }
+
+            if (fileLock != null) {
+                try {
+                    fileLock.release();
+                } catch (IOException e) {
+                    Log_OC.e(TAG, "Failed to unlock file with path " + ocFile.getStoragePath());
+                }
+            }
+
+            if (temporalFile != null && !originalFile.equals(temporalFile)) {
+                temporalFile.delete();
+            }
+            if (result == null) {
+                result = new RemoteOperationResult(ResultCode.UNKNOWN_ERROR);
+            }
+            if (result.isSuccess()) {
+                Log_OC.i(TAG, "Upload of " + ocFile.getStoragePath() + " to " + ocFile.getRemotePath() + ": " +
+                        result.getLogMessage());
+            } else {
+                if (result.getException() != null) {
+                    if (result.isCancelled()) {
+                        Log_OC.w(TAG, "Upload of " + ocFile.getStoragePath() + " to " + ocFile.getRemotePath() +
+                                ": " + result.getLogMessage());
+                    } else {
+                        Log_OC.e(TAG, "Upload of " + ocFile.getStoragePath() + " to " + ocFile.getRemotePath() +
+                                ": " + result.getLogMessage(), result.getException());
+                    }
+
+                } else {
+                    Log_OC.e(TAG, "Upload of " + ocFile.getStoragePath() + " to " + ocFile.getRemotePath() +
+                            ": " + result.getLogMessage());
+                }
+            }
+        }
+
+        switch (localBehaviour) {
+            case FileUploader.LOCAL_BEHAVIOUR_FORGET:
+                String temporalPath = FileStorageUtils.getTemporalPath(account.name) + ocFile.getRemotePath();
+                if (originalStoragePath.equals(temporalPath)) {
+                    // delete local file is was pre-copied in temporary folder (see .ui.helpers.UriUploader)
+                    temporalFile = new File(temporalPath);
+                    temporalFile.delete();
+                }
+                ocFile.setStoragePath("");
+                saveUploadedFile(client);
+                break;
+
+            case FileUploader.LOCAL_BEHAVIOUR_DELETE:
+                Log_OC.d(TAG, "Delete source file");
+
+                originalFile.delete();
+                getStorageManager().deleteFileInMediaScan(originalFile.getAbsolutePath());
+                saveUploadedFile(client);
+                break;
+
+            case FileUploader.LOCAL_BEHAVIOUR_COPY:
+                if (temporalFile != null) {
+                    try {
+                        move(temporalFile, expectedFile);
+                    } catch (IOException e) {
+                        e.printStackTrace();
+                    }
+                }
+                ocFile.setStoragePath(expectedFile.getAbsolutePath());
+                saveUploadedFile(client);
+                FileDataStorageManager.triggerMediaScan(expectedFile.getAbsolutePath());
+                break;
+
+            case FileUploader.LOCAL_BEHAVIOUR_MOVE:
+
+                String expectedPath = FileStorageUtils.getDefaultSavePathFor(account.name, ocFile);
+                expectedFile = new File(expectedPath);
+
+                try {
+                    move(originalFile, expectedFile);
+                } catch (IOException e) {
+                    e.printStackTrace();
+                }
+                getStorageManager().deleteFileInMediaScan(originalFile.getAbsolutePath());
+                ocFile.setStoragePath(expectedFile.getAbsolutePath());
+                saveUploadedFile(client);
+                FileDataStorageManager.triggerMediaScan(expectedFile.getAbsolutePath());
+                break;
+        }
+
+        return result;
+    }
+
+    /**
+     * Checks the existence of the folder where the current file will be uploaded both
+     * in the remote server and in the local database.
+     * <p/>
+     * If the upload is set to enforce the creation of the folder, the method tries to
+     * create it both remote and locally.
+     *
+     * @param pathToGrant Full remote path whose existence will be granted.
+     * @return An {@link OCFile} instance corresponding to the folder where the file
+     * will be uploaded.
+     */
+    private RemoteOperationResult grantFolderExistence(String pathToGrant, OwnCloudClient client) {
+        RemoteOperation operation = new ExistenceCheckRemoteOperation(pathToGrant, context, false);
+        RemoteOperationResult result = operation.execute(client);
+        if (!result.isSuccess() && result.getCode() == ResultCode.FILE_NOT_FOUND && mRemoteFolderToBeCreated) {
+            SyncOperation syncOp = new CreateFolderOperation(pathToGrant, true);
+            result = syncOp.execute(client, getStorageManager());
+        }
+        if (result.isSuccess()) {
+            OCFile parentDir = getStorageManager().getFileByPath(pathToGrant);
+            if (parentDir == null) {
+                parentDir = createLocalFolder(pathToGrant);
+            }
+            if (parentDir != null) {
+                result = new RemoteOperationResult(ResultCode.OK);
+            } else {
+                result = new RemoteOperationResult(ResultCode.UNKNOWN_ERROR);
+            }
+        }
+        return result;
+    }
+
+    private OCFile createLocalFolder(String remotePath) {
+        String parentPath = new File(remotePath).getParent();
+        parentPath = parentPath.endsWith(OCFile.PATH_SEPARATOR) ?
+                parentPath : parentPath + OCFile.PATH_SEPARATOR;
+        OCFile parent = getStorageManager().getFileByPath(parentPath);
+        if (parent == null) {
+            parent = createLocalFolder(parentPath);
+        }
+        if (parent != null) {
+            OCFile createdFolder = new OCFile(remotePath);
+            createdFolder.setMimetype(MimeType.DIRECTORY);
+            createdFolder.setParentId(parent.getFileId());
+            getStorageManager().saveFile(createdFolder);
+            return createdFolder;
+        }
+        return null;
+    }
+
+    /**
+     * Checks if remotePath does not exist in the server and returns it, or adds
+     * a suffix to it in order to avoid the server file is overwritten.
+     *
+     * @param wc
+     * @param remotePath
+     * @return
+     */
+    private String getAvailableRemotePath(OwnCloudClient wc, String remotePath) {
+        boolean check = existsFile(wc, remotePath);
+        if (!check) {
+            return remotePath;
+        }
+
+        int pos = remotePath.lastIndexOf('.');
+        String suffix = "";
+        String extension = "";
+        if (pos >= 0) {
+            extension = remotePath.substring(pos + 1);
+            remotePath = remotePath.substring(0, pos);
+        }
+        int count = 2;
+        do {
+            suffix = " (" + count + ")";
+            if (pos >= 0) {
+                check = existsFile(wc, remotePath + suffix + "." + extension);
+            } else {
+                check = existsFile(wc, remotePath + suffix);
+            }
+            count++;
+        } while (check);
+
+        if (pos >= 0) {
+            return remotePath + suffix + "." + extension;
+        } else {
+            return remotePath + suffix;
+        }
+    }
+
+    private boolean existsFile(OwnCloudClient client, String remotePath) {
+        ExistenceCheckRemoteOperation existsOperation =
+                new ExistenceCheckRemoteOperation(remotePath, context, false);
+        RemoteOperationResult result = existsOperation.execute(client);
+        return result.isSuccess();
+    }
+
+    /**
+     * Allows to cancel the actual upload operation. If actual upload operating
+     * is in progress it is cancelled, if upload preparation is being performed
+     * upload will not take place.
+     */
+    public void cancel() {
+        if (mUploadOperation == null) {
+            if (mUploadStarted.get()) {
+                Log_OC.d(TAG, "Cancelling upload during upload preparations.");
+                mCancellationRequested.set(true);
+            } else {
+                Log_OC.e(TAG, "No upload in progress. This should not happen.");
+            }
+        } else {
+            Log_OC.d(TAG, "Cancelling upload during actual upload operation.");
+            mUploadOperation.cancel();
+        }
+    }
+
+    /**
+     * As soon as this method return true, upload can be cancel via cancel().
+     */
+    public boolean isUploadInProgress() {
+        return mUploadStarted.get();
+
+    }
+
+//    /**
+//     * TODO rewrite with homogeneous fail handling, remove dependency on {@link RemoteOperationResult},
+//     * TODO     use Exceptions instead
+//     *
+//     * @param sourceFile Source file to copy.
+//     * @param targetFile Target location to copy the file.
+//     * @return {@link RemoteOperationResult}
+//     * @throws IOException
+//     */
+//    private RemoteOperationResult copy(File sourceFile, File targetFile) throws IOException {
+//        Log_OC.d(TAG, "Copying local file");
+//
+//        RemoteOperationResult result = null;
+//
+//        if (FileStorageUtils.getUsableSpace(account.name) < sourceFile.length()) {
+//            result = new RemoteOperationResult(ResultCode.LOCAL_STORAGE_FULL);
+//            return result;  // error condition when the file should be copied
+//
+//        } else {
+//            Log_OC.d(TAG, "Creating temporal folder");
+//            File temporalParent = targetFile.getParentFile();
+//            temporalParent.mkdirs();
+//            if (!temporalParent.isDirectory()) {
+//                throw new IOException(
+//                        "Unexpected error: parent directory could not be created");
+//            }
+//            Log_OC.d(TAG, "Creating temporal file");
+//            targetFile.createNewFile();
+//            if (!targetFile.isFile()) {
+//                throw new IOException(
+//                        "Unexpected error: target file could not be created");
+//            }
+//
+//            Log_OC.d(TAG, "Copying file contents");
+//            InputStream in = null;
+//            OutputStream out = null;
+//
+//            try {
+//                if (!mOriginalStoragePath.equals(targetFile.getAbsolutePath())) {
+//                    // In case document provider schema as 'content://'
+//                    if (mOriginalStoragePath.startsWith(UriUtils.URI_CONTENT_SCHEME)) {
+//                        Uri uri = Uri.parse(mOriginalStoragePath);
+//                        in = context.getContentResolver().openInputStream(uri);
+//                    } else {
+//                        in = new FileInputStream(sourceFile);
+//                    }
+//                    out = new FileOutputStream(targetFile);
+//                    int nRead;
+//                    byte[] buf = new byte[4096];
+//                    while (!mCancellationRequested.get() &&
+//                            (nRead = in.read(buf)) > -1) {
+//                        out.write(buf, 0, nRead);
+//                    }
+//                    out.flush();
+//
+//                } // else: weird but possible situation, nothing to copy
+//
+//                if (mCancellationRequested.get()) {
+//                    result = new RemoteOperationResult(new OperationCancelledException());
+//                    return result;
+//                }
+//
+//            } catch (Exception e) {
+//                result = new RemoteOperationResult(ResultCode.LOCAL_STORAGE_NOT_COPIED);
+//                return result;
+//
+//            } finally {
+//                try {
+//                    if (in != null) {
+//                        in.close();
+//                    }
+//                } catch (Exception e) {
+//                    Log_OC.d(TAG, "Weird exception while closing input stream for " +
+//                            mOriginalStoragePath + " (ignoring)", e);
+//                }
+//                try {
+//                    if (out != null) {
+//                        out.close();
+//                    }
+//                } catch (Exception e) {
+//                    Log_OC.d(TAG, "Weird exception while closing output stream for " +
+//                            targetFile.getAbsolutePath() + " (ignoring)", e);
+//                }
+//            }
+//        }
+//        return result;
+//    }
+
+
+    /**
+     * TODO rewrite with homogeneous fail handling, remove dependency on {@link RemoteOperationResult},
+     * TODO     use Exceptions instead
+     * <p>
+     * TODO refactor both this and 'copy' in a single method
+     *
+     * @param sourceFile Source file to move.
+     * @param targetFile Target location to move the file.
+     * @return {@link RemoteOperationResult}
+     * @throws IOException
+     */
+    private void move(File sourceFile, File targetFile) throws IOException {
+
+        if (!targetFile.equals(sourceFile)) {
+            File expectedFolder = targetFile.getParentFile();
+            expectedFolder.mkdirs();
+
+            if (expectedFolder.isDirectory()) {
+                if (!sourceFile.renameTo(targetFile)) {
+                    // try to copy and then delete
+                    targetFile.createNewFile();
+                    FileChannel inChannel = new FileInputStream(sourceFile).getChannel();
+                    FileChannel outChannel = new FileOutputStream(targetFile).getChannel();
+                    try {
+                        inChannel.transferTo(0, inChannel.size(), outChannel);
+                        sourceFile.delete();
+                    } catch (Exception e) {
+                        ocFile.setStoragePath(""); // forget the local file
+                        // by now, treat this as a success; the file was uploaded
+                        // the best option could be show a warning message
+                    } finally {
+                        if (inChannel != null) {
+                            inChannel.close();
+                        }
+                        if (outChannel != null) {
+                            outChannel.close();
+                        }
+                    }
+                }
+
+            } else {
+                ocFile.setStoragePath("");
+            }
+        }
+    }
+
+    /**
+     * Saves a OC File after a successful upload.
+     * <p/>
+     * A PROPFIND is necessary to keep the props in the local database
+     * synchronized with the server, specially the modification time and Etag
+     * (where available)
+     * <p/>
+     */
+    private void saveUploadedFile(OwnCloudClient client) {
+        OCFile file = ocFile;
+        if (file.fileExists()) {
+            file = getStorageManager().getFileById(file.getFileId());
+        }
+        long syncDate = System.currentTimeMillis();
+        file.setLastSyncDateForData(syncDate);
+
+        // new PROPFIND to keep data consistent with server
+        // in theory, should return the same we already have
+        // TODO from the appropriate OC server version, get data from last PUT response headers, instead
+        // TODO     of a new PROPFIND; the latter may fail, specially for chunked uploads
+        ReadRemoteFileOperation operation = new ReadRemoteFileOperation(getRemotePath());
+        RemoteOperationResult result = operation.execute(client);
+        if (result.isSuccess()) {
+            updateOCFile(file, (RemoteFile) result.getData().get(0));
+            file.setLastSyncDateForProperties(syncDate);
+        } else {
+            Log_OC.e(TAG, "Error reading properties of file after successful upload; this is gonna hurt...");
+        }
+
+        file.setEncrypted(true);
+        file.setStoragePath("");
+        file.setParentId(parentFile.getFileId());
+        getStorageManager().saveFile(file);
+        getStorageManager().saveConflict(file, null);
+
+        FileDataStorageManager.triggerMediaScan(file.getStoragePath());
+    }
+
+    private void updateOCFile(OCFile file, RemoteFile remoteFile) {
+        file.setCreationTimestamp(remoteFile.getCreationTimestamp());
+        file.setFileLength(remoteFile.getLength());
+//        file.setMimetype(file.getMimetype());
+        file.setModificationTimestamp(remoteFile.getModifiedTimestamp());
+        file.setModificationTimestampAtLastSyncForData(remoteFile.getModifiedTimestamp());
+        file.setEtag(remoteFile.getEtag());
+        file.setRemoteId(remoteFile.getRemoteId());
+    }
+
+    public interface OnRenameListener {
+
+        void onRenameUpload();
+    }
+
+}

+ 21 - 23
src/main/java/com/owncloud/android/operations/UploadFileOperation.java

@@ -560,15 +560,15 @@ public class UploadFileOperation extends SyncOperation {
                 size = new File(mFile.getStoragePath()).length();
                 size = new File(mFile.getStoragePath()).length();
             }
             }
 
 
-            for (OCUpload ocUpload : uploadsStorageManager.getAllStoredUploads()) {
-                if (ocUpload.getUploadId() == getOCUploadId()) {
-                    ocUpload.setFileSize(size);
-                    uploadsStorageManager.updateUpload(ocUpload);
-                    break;
-                }
-            }
+        boolean metadataExists = false;
+            String token = null;
+                
+                ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProvider(getContext().getContentResolver());
+                
+            String privateKey = arbitraryDataProvider.getValue(getAccount().name, EncryptionUtils.PRIVATE_KEY);
+        String publicKey = arbitraryDataProvider.getValue(getAccount().name, EncryptionUtils.PUBLIC_KEY);
 
 
-            /// perform the upload
+        /// perform the upload
             if (mChunked && (size > ChunkedUploadRemoteFileOperation.CHUNK_SIZE)) {
             if (mChunked && (size > ChunkedUploadRemoteFileOperation.CHUNK_SIZE)) {
                 mUploadOperation = new ChunkedUploadRemoteFileOperation(mContext, encryptedTempFile.getAbsolutePath(),
                 mUploadOperation = new ChunkedUploadRemoteFileOperation(mContext, encryptedTempFile.getAbsolutePath(),
                         mFile.getParentRemotePath() + encryptedFileName, mFile.getMimetype(),
                         mFile.getParentRemotePath() + encryptedFileName, mFile.getMimetype(),
@@ -589,8 +589,9 @@ public class UploadFileOperation extends SyncOperation {
             }
             }
 
 
 //            FileChannel channel = null;
 //            FileChannel channel = null;
-//            try {
-//                channel = new RandomAccessFile(ocFile.getStoragePath(), "rw").getChannel();
+//try {
+
+    //                channel = new RandomAccessFile(ocFile.getStoragePath(), "rw").getChannel();
 //                fileLock = channel.tryLock();
 //                fileLock = channel.tryLock();
 //            } catch (FileNotFoundException e) {
 //            } catch (FileNotFoundException e) {
 //                if (temporalFile == null) {
 //                if (temporalFile == null) {
@@ -756,14 +757,11 @@ public class UploadFileOperation extends SyncOperation {
         if (Device.getNetworkType(mContext).equals(JobRequest.NetworkType.ANY) ||
         if (Device.getNetworkType(mContext).equals(JobRequest.NetworkType.ANY) ||
                 ConnectivityUtils.isInternetWalled(mContext)) {
                 ConnectivityUtils.isInternetWalled(mContext)) {
             return new RemoteOperationResult(ResultCode.NO_NETWORK_CONNECTION);
             return new RemoteOperationResult(ResultCode.NO_NETWORK_CONNECTION);
-        }
-        
-        /// Check that connectivity conditions are met and delays the upload otherwise
-        // TODO verify if this can be deleted
-        if (mOnWifiOnly && !Device.getNetworkType(mContext).equals(JobRequest.NetworkType.UNMETERED)) {
-            Log_OC.d(TAG, "Upload delayed until WiFi is available: " + getRemotePath());
-            return new RemoteOperationResult(ResultCode.DELAYED_FOR_WIFI);
-        }
+        }        /// Check that connectivity conditions are met and delays the upload otherwise// TODO verify if this can be deleted
+            if (mOnWifiOnly && !Device.getNetworkType(mContext).equals(JobRequest.NetworkType.UNMETERED)) {
+                Log_OC.d(TAG, "Upload delayed until WiFi is available: " + getRemotePath());
+                return new RemoteOperationResult(ResultCode.DELAYED_FOR_WIFI);
+            }
 
 
         // TODO verify if this can be deleted
         // TODO verify if this can be deleted
         // Check if charging conditions are met and delays the upload otherwise
         // Check if charging conditions are met and delays the upload otherwise
@@ -1110,6 +1108,7 @@ public class UploadFileOperation extends SyncOperation {
      * @param client     OwnCloud client
      * @param client     OwnCloud client
      * @param remotePath remote path of the file
      * @param remotePath remote path of the file
      * @param metadata   metadata of encrypted folder
      * @param metadata   metadata of encrypted folder
+     * @param metadata
      * @return new remote path
      * @return new remote path
      */
      */
     private String getAvailableRemotePath(OwnCloudClient client, String remotePath, DecryptedFolderMetadata metadata,
     private String getAvailableRemotePath(OwnCloudClient client, String remotePath, DecryptedFolderMetadata metadata,
@@ -1158,11 +1157,10 @@ public class UploadFileOperation extends SyncOperation {
 
 
             return false;
             return false;
         } else {
         } else {
-            ExistenceCheckRemoteOperation existsOperation = new ExistenceCheckRemoteOperation(remotePath, mContext,
-                    false);
-            RemoteOperationResult result = existsOperation.execute(client);
-            return result.isSuccess();
-        }
+        ExistenceCheckRemoteOperation existsOperation =
+                new ExistenceCheckRemoteOperation(remotePath, mContext, false);
+        RemoteOperationResult result = existsOperation.execute(client);
+        return result.isSuccess();}
     }
     }
 
 
     /**
     /**

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

@@ -52,6 +52,7 @@ import android.support.v4.view.MenuItemCompat;
 import android.support.v7.app.AlertDialog;
 import android.support.v7.app.AlertDialog;
 import android.support.v7.widget.SearchView;
 import android.support.v7.widget.SearchView;
 import android.text.TextUtils;
 import android.text.TextUtils;
+import android.util.Log;
 import android.view.Menu;
 import android.view.Menu;
 import android.view.MenuInflater;
 import android.view.MenuInflater;
 import android.view.MenuItem;
 import android.view.MenuItem;
@@ -105,6 +106,7 @@ import com.owncloud.android.ui.preview.PreviewMediaFragment;
 import com.owncloud.android.ui.preview.PreviewTextFragment;
 import com.owncloud.android.ui.preview.PreviewTextFragment;
 import com.owncloud.android.ui.preview.PreviewVideoActivity;
 import com.owncloud.android.ui.preview.PreviewVideoActivity;
 import com.owncloud.android.utils.DataHolderUtil;
 import com.owncloud.android.utils.DataHolderUtil;
+import com.owncloud.android.utils.EncryptionUtils;
 import com.owncloud.android.utils.DisplayUtils;
 import com.owncloud.android.utils.DisplayUtils;
 import com.owncloud.android.utils.ErrorMessageAdapter;
 import com.owncloud.android.utils.ErrorMessageAdapter;
 import com.owncloud.android.utils.FileSortOrder;
 import com.owncloud.android.utils.FileSortOrder;

+ 9 - 0
src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java

@@ -99,6 +99,7 @@ import com.owncloud.android.ui.preview.PreviewTextFragment;
 import com.owncloud.android.utils.AnalyticsUtils;
 import com.owncloud.android.utils.AnalyticsUtils;
 import com.owncloud.android.utils.DisplayUtils;
 import com.owncloud.android.utils.DisplayUtils;
 import com.owncloud.android.utils.EncryptionUtils;
 import com.owncloud.android.utils.EncryptionUtils;
+import com.owncloud.android.utils.EncryptionUtils;
 import com.owncloud.android.utils.FileSortOrder;
 import com.owncloud.android.utils.FileSortOrder;
 import com.owncloud.android.utils.MimeTypeUtil;
 import com.owncloud.android.utils.MimeTypeUtil;
 import com.owncloud.android.utils.ThemeUtils;
 import com.owncloud.android.utils.ThemeUtils;
@@ -1036,6 +1037,14 @@ public class OCFileListFragment extends ExtendedListFragment implements OCFileLi
                     mContainerActivity.getFileOperationsHelper().toggleEncryption(singleFile, false);
                     mContainerActivity.getFileOperationsHelper().toggleEncryption(singleFile, false);
                     return true;
                     return true;
                 }
                 }
+                case R.id. action_encrypted: {
+                    mContainerActivity.getFileOperationsHelper().toggleEncryption(singleFile, true);
+                    return true;
+                }
+                case R.id. action_unset_encrypted: {
+                    mContainerActivity.getFileOperationsHelper().toggleEncryption(singleFile, false);
+                    return true;
+                }
             }
             }
         }
         }
 
 

+ 3 - 1
src/modified/AndroidManifest.xml

@@ -18,7 +18,9 @@
 -->
 -->
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           xmlns:tools="http://schemas.android.com/tools"
           xmlns:tools="http://schemas.android.com/tools"
-          package="com.owncloud.android">
+          package="com.owncloud.android"
+          android:versionCode="20000052"
+          android:versionName="2.0.0-e2e-02">
 
 
     <application
     <application
         android:name=".MainApp"
         android:name=".MainApp"

File diff suppressed because it is too large
+ 57 - 0
src/test/java/com/owncloud/android/utils/EncryptionTest.java


Some files were not shown because too many files changed in this diff