浏览代码

Prevent crash when selecting too many files to copy or move (#10476)

* Prevent crash when selecting too many files to copy or move

This is caused by too much data in intent extras as we're passing a fat arraylist of objects.
As we only need the paths for the copy operation, change it so that only the paths are passed.

Ideally this would instead be stored in FDA or whatever listener we have for the folder picker,
rather than passed back and forth between the folder picker.

Signed-off-by: Álvaro Brey <alvaro.brey@nextcloud.com>

* Convert FolderPickerActivity to Kotlin (step 1)

Signed-off-by: Álvaro Brey <alvaro.brey@nextcloud.com>

* FolderPickerActivity: convert to Kotlin step 2

Signed-off-by: Álvaro Brey <alvaro.brey@nextcloud.com>

* OCFileListFragment: deduplicate copy/move code

Signed-off-by: Álvaro Brey <alvaro.brey@nextcloud.com>
Álvaro Brey 2 年之前
父节点
当前提交
e8d2989295

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

@@ -965,9 +965,9 @@ public class FileDisplayActivity extends FileActivity
      * @param data Intent received
      */
     private void requestMoveOperation(Intent data) {
-        OCFile folderToMoveAt = data.getParcelableExtra(FolderPickerActivity.EXTRA_FOLDER);
-        ArrayList<OCFile> files = data.getParcelableArrayListExtra(FolderPickerActivity.EXTRA_FILES);
-        getFileOperationsHelper().moveFiles(files, folderToMoveAt);
+        final OCFile folderToMoveAt = data.getParcelableExtra(FolderPickerActivity.EXTRA_FOLDER);
+        final List<String> filePaths = data.getStringArrayListExtra(FolderPickerActivity.EXTRA_FILE_PATHS);
+        getFileOperationsHelper().moveFiles(filePaths, folderToMoveAt);
     }
 
     /**
@@ -976,9 +976,9 @@ public class FileDisplayActivity extends FileActivity
      * @param data Intent received
      */
     private void requestCopyOperation(Intent data) {
-        OCFile folderToMoveAt = data.getParcelableExtra(FolderPickerActivity.EXTRA_FOLDER);
-        ArrayList<OCFile> files = data.getParcelableArrayListExtra(FolderPickerActivity.EXTRA_FILES);
-        getFileOperationsHelper().copyFiles(files, folderToMoveAt);
+        final OCFile targetFolder = data.getParcelableExtra(FolderPickerActivity.EXTRA_FOLDER);
+        final List<String> filePaths = data.getStringArrayListExtra(FolderPickerActivity.EXTRA_FILE_PATHS);
+        getFileOperationsHelper().copyFiles(filePaths, targetFolder);
     }
 
     private boolean isSearchOpen() {

+ 0 - 602
app/src/main/java/com/owncloud/android/ui/activity/FolderPickerActivity.java

@@ -1,602 +0,0 @@
-/*
- *   ownCloud Android client application
- *
- *   Copyright (C) 2016 ownCloud Inc.
- *
- *   This program is free software: you can redistribute it and/or modify
- *   it under the terms of the GNU General Public License version 2,
- *   as published by the Free Software Foundation.
- *
- *   This program is distributed in the hope that it will be useful,
- *   but WITHOUT ANY WARRANTY; without even the implied warranty of
- *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *   GNU General Public License for more details.
- *
- *   You should have received a copy of the GNU General Public License
- *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
- *
- */
-
-package com.owncloud.android.ui.activity;
-
-import android.accounts.AuthenticatorException;
-import android.app.Activity;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.res.Resources.NotFoundException;
-import android.os.Bundle;
-import android.os.Parcelable;
-import android.util.Log;
-import android.view.ActionMode;
-import android.view.Menu;
-import android.view.MenuInflater;
-import android.view.MenuItem;
-import android.view.View;
-import android.view.View.OnClickListener;
-
-import com.google.android.material.button.MaterialButton;
-import com.nextcloud.client.di.Injectable;
-import com.owncloud.android.R;
-import com.owncloud.android.datamodel.FileDataStorageManager;
-import com.owncloud.android.datamodel.OCFile;
-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.SearchRemoteOperation;
-import com.owncloud.android.operations.CreateFolderOperation;
-import com.owncloud.android.operations.RefreshFolderOperation;
-import com.owncloud.android.syncadapter.FileSyncAdapter;
-import com.owncloud.android.ui.dialog.CreateFolderDialogFragment;
-import com.owncloud.android.ui.dialog.SortingOrderDialogFragment;
-import com.owncloud.android.ui.events.SearchEvent;
-import com.owncloud.android.ui.fragment.FileFragment;
-import com.owncloud.android.ui.fragment.OCFileListFragment;
-import com.owncloud.android.utils.DataHolderUtil;
-import com.owncloud.android.utils.DisplayUtils;
-import com.owncloud.android.utils.ErrorMessageAdapter;
-import com.owncloud.android.utils.FileSortOrder;
-
-import java.io.File;
-import java.util.ArrayList;
-
-import javax.inject.Inject;
-
-import androidx.appcompat.app.ActionBar;
-import androidx.fragment.app.Fragment;
-import androidx.fragment.app.FragmentTransaction;
-import androidx.localbroadcastmanager.content.LocalBroadcastManager;
-
-public class FolderPickerActivity extends FileActivity implements FileFragment.ContainerActivity,
-    OnClickListener,
-    OnEnforceableRefreshListener,
-    Injectable,
-    SortingOrderDialogFragment.OnSortingOrderListener {
-
-    public static final String EXTRA_FOLDER = FolderPickerActivity.class.getCanonicalName() + ".EXTRA_FOLDER";
-    public static final String EXTRA_FILES = FolderPickerActivity.class.getCanonicalName() + ".EXTRA_FILES";
-    public static final String EXTRA_ACTION = FolderPickerActivity.class.getCanonicalName() + ".EXTRA_ACTION";
-    public static final String EXTRA_CURRENT_FOLDER = FolderPickerActivity.class.getCanonicalName() +
-        ".EXTRA_CURRENT_FOLDER";
-    public static final String MOVE = "MOVE";
-    public static final String COPY = "COPY";
-
-    private SyncBroadcastReceiver mSyncBroadcastReceiver;
-
-    private static final String TAG = FolderPickerActivity.class.getSimpleName();
-
-    protected static final String TAG_LIST_OF_FOLDERS = "LIST_OF_FOLDERS";
-
-    private boolean mSyncInProgress;
-
-    private boolean mSearchOnlyFolders;
-    private boolean mDoNotEnterEncryptedFolder;
-
-    protected MaterialButton mCancelBtn;
-    protected MaterialButton mChooseBtn;
-    private String caption;
-    @Inject LocalBroadcastManager localBroadcastManager;
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        Log_OC.d(TAG, "onCreate() start");
-        super.onCreate(savedInstanceState);
-
-        if (this instanceof FilePickerActivity) {
-            setContentView(R.layout.files_picker);
-        } else {
-            setContentView(R.layout.files_folder_picker);
-        }
-
-        // sets callback listeners for UI elements
-        initControls();
-
-        // Action bar setup
-        setupToolbar();
-        findViewById(R.id.sort_list_button_group).setVisibility(View.VISIBLE);
-        findViewById(R.id.switch_grid_view_button).setVisibility(View.GONE);
-
-        if (getIntent().getStringExtra(EXTRA_ACTION) != null) {
-            switch (getIntent().getStringExtra(EXTRA_ACTION)) {
-                case MOVE:
-                    caption = getResources().getText(R.string.move_to).toString();
-                    mSearchOnlyFolders = true;
-                    mDoNotEnterEncryptedFolder = true;
-                    break;
-                case COPY:
-                    caption = getResources().getText(R.string.copy_to).toString();
-                    mSearchOnlyFolders = true;
-                    mDoNotEnterEncryptedFolder = true;
-                    break;
-                default:
-                    caption = themeUtils.getDefaultDisplayNameForRootFolder(this);
-                    break;
-            }
-        } else {
-            caption = themeUtils.getDefaultDisplayNameForRootFolder(this);
-        }
-
-        if (getIntent().getParcelableExtra(EXTRA_CURRENT_FOLDER) != null) {
-            setFile(getIntent().getParcelableExtra(EXTRA_CURRENT_FOLDER));
-        }
-
-        if (savedInstanceState == null) {
-            createFragments();
-        }
-
-        updateActionBarTitleAndHomeButtonByString(caption);
-
-        // always AFTER setContentView(...) ; to work around bug in its implementation
-
-        // sets message for empty list of folders
-        setBackgroundText();
-
-        Log_OC.d(TAG, "onCreate() end");
-    }
-
-    @Override
-    public void onActionModeStarted(ActionMode mode) {
-        super.onActionModeStarted(mode);
-        if (getAccount() != null) {
-
-            updateFileFromDB();
-
-            OCFile folder = getFile();
-            if (folder == null || !folder.isFolder()) {
-                // fall back to root folder
-                setFile(getStorageManager().getFileByPath(OCFile.ROOT_PATH));
-                folder = getFile();
-            }
-
-            OCFileListFragment listOfFolders = getListOfFilesFragment();
-            listOfFolders.listDirectory(folder, false, false);
-            startSyncFolderOperation(folder, false);
-
-            updateNavigationElementsInActionBar();
-        }
-    }
-
-    private Activity getActivity() {
-        return this;
-    }
-
-    protected void createFragments() {
-        OCFileListFragment listOfFiles = new OCFileListFragment();
-        Bundle args = new Bundle();
-        args.putBoolean(OCFileListFragment.ARG_ONLY_FOLDERS_CLICKABLE, true);
-        args.putBoolean(OCFileListFragment.ARG_HIDE_FAB, true);
-        args.putBoolean(OCFileListFragment.ARG_HIDE_ITEM_OPTIONS, true);
-        args.putBoolean(OCFileListFragment.ARG_SEARCH_ONLY_FOLDER, mSearchOnlyFolders);
-        listOfFiles.setArguments(args);
-        FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
-        transaction.add(R.id.fragment_container, listOfFiles, TAG_LIST_OF_FOLDERS);
-        transaction.commit();
-    }
-
-    /**
-     * Show a text message on screen view for notifying user if content is loading or folder is empty
-     */
-    private void setBackgroundText() {
-        OCFileListFragment listFragment = getListOfFilesFragment();
-        if (listFragment != null) {
-            if (!mSyncInProgress) {
-                listFragment.setMessageForEmptyList(
-                    R.string.file_list_empty_headline,
-                    R.string.file_list_empty_moving,
-                    R.drawable.ic_list_empty_create_folder,
-                    true
-                );
-            } else {
-                listFragment.setEmptyListLoadingMessage();
-            }
-        } else {
-            Log.e(TAG, "OCFileListFragment is null");
-        }
-    }
-
-    protected OCFileListFragment getListOfFilesFragment() {
-        Fragment listOfFiles = getSupportFragmentManager().findFragmentByTag(FolderPickerActivity.TAG_LIST_OF_FOLDERS);
-        if (listOfFiles != null) {
-            return (OCFileListFragment) listOfFiles;
-        }
-        Log_OC.e(TAG, "Access to non existing list of files fragment!!");
-        return null;
-    }
-
-    /**
-     * {@inheritDoc}
-     * <p>
-     * Updates action bar and second fragment, if in dual pane mode.
-     */
-    @Override
-    public void onBrowsedDownTo(OCFile directory) {
-        setFile(directory);
-        updateNavigationElementsInActionBar();
-        // Sync Folder
-        startSyncFolderOperation(directory, false);
-    }
-
-    @Override
-    public void onSavedCertificate() {
-        startSyncFolderOperation(getCurrentDir(), false);
-    }
-
-    public void startSyncFolderOperation(OCFile folder, boolean ignoreETag) {
-        long currentSyncTime = System.currentTimeMillis();
-
-        mSyncInProgress = true;
-
-        // perform folder synchronization
-        RemoteOperation refreshFolderOperation = new RefreshFolderOperation(folder,
-                                                                            currentSyncTime,
-                                                                            false,
-                                                                            ignoreETag,
-                                                                            getStorageManager(),
-                                                                            getUser().orElseThrow(RuntimeException::new),
-                                                                            getApplicationContext());
-
-        refreshFolderOperation.execute(getAccount(), this, null, null);
-        getListOfFilesFragment().setLoading(true);
-        setBackgroundText();
-    }
-
-    @Override
-    protected void onResume() {
-        super.onResume();
-        Log_OC.e(TAG, "onResume() start");
-        getListOfFilesFragment().setLoading(mSyncInProgress);
-
-
-        // refresh list of files
-        refreshListOfFilesFragment(false);
-
-        // Listen for sync messages
-        IntentFilter syncIntentFilter = new IntentFilter(FileSyncAdapter.EVENT_FULL_SYNC_START);
-        syncIntentFilter.addAction(FileSyncAdapter.EVENT_FULL_SYNC_END);
-        syncIntentFilter.addAction(FileSyncAdapter.EVENT_FULL_SYNC_FOLDER_CONTENTS_SYNCED);
-        syncIntentFilter.addAction(RefreshFolderOperation.EVENT_SINGLE_FOLDER_CONTENTS_SYNCED);
-        syncIntentFilter.addAction(RefreshFolderOperation.EVENT_SINGLE_FOLDER_SHARES_SYNCED);
-        mSyncBroadcastReceiver = new SyncBroadcastReceiver();
-        localBroadcastManager.registerReceiver(mSyncBroadcastReceiver, syncIntentFilter);
-
-        Log_OC.d(TAG, "onResume() end");
-    }
-
-    @Override
-    protected void onPause() {
-        Log_OC.e(TAG, "onPause() start");
-        if (mSyncBroadcastReceiver != null) {
-            localBroadcastManager.unregisterReceiver(mSyncBroadcastReceiver);
-            mSyncBroadcastReceiver = null;
-        }
-
-        Log_OC.d(TAG, "onPause() end");
-        super.onPause();
-    }
-
-    @Override
-    public boolean onCreateOptionsMenu(Menu menu) {
-        MenuInflater inflater = getMenuInflater();
-        inflater.inflate(R.menu.activity_folder_picker, menu);
-        return true;
-    }
-
-    @Override
-    public boolean onOptionsItemSelected(MenuItem item) {
-        boolean retval = true;
-        int itemId = item.getItemId();
-
-        if (itemId == R.id.action_create_dir) {
-            CreateFolderDialogFragment dialog = CreateFolderDialogFragment.newInstance(getCurrentFolder());
-            dialog.show(getSupportFragmentManager(), CreateFolderDialogFragment.CREATE_FOLDER_FRAGMENT);
-        } else if (itemId == android.R.id.home) {
-            OCFile currentDir = getCurrentFolder();
-            if (currentDir != null && currentDir.getParentId() != 0) {
-                onBackPressed();
-            }
-        } else {
-            retval = super.onOptionsItemSelected(item);
-        }
-
-        return retval;
-    }
-
-    protected OCFile getCurrentFolder() {
-        OCFile currentFile = getFile();
-        OCFile finalFolder = null;
-        FileDataStorageManager storageManager = getStorageManager();
-
-        // If the file is null, take the root folder to avoid any error in functions depending on this one
-        if (currentFile != null) {
-            if (currentFile.isFolder()) {
-                finalFolder = currentFile;
-            } else if (currentFile.getRemotePath() != null) {
-                String parentPath = new File(currentFile.getRemotePath()).getParent();
-                finalFolder = storageManager.getFileByPath(parentPath);
-            }
-        } else {
-            finalFolder = storageManager.getFileByPath(OCFile.ROOT_PATH);
-        }
-        return finalFolder;
-    }
-
-    public void refreshListOfFilesFragment(boolean fromSearch) {
-        OCFileListFragment fileListFragment = getListOfFilesFragment();
-        if (fileListFragment != null) {
-            fileListFragment.listDirectory(false, fromSearch);
-        }
-    }
-
-    public void browseToRoot() {
-        OCFileListFragment listOfFiles = getListOfFilesFragment();
-        if (listOfFiles != null) {  // should never be null, indeed
-            OCFile root = getStorageManager().getFileByPath(OCFile.ROOT_PATH);
-            listOfFiles.listDirectory(root, false, false);
-            setFile(listOfFiles.getCurrentFile());
-            updateNavigationElementsInActionBar();
-            startSyncFolderOperation(root, false);
-        }
-    }
-
-    @Override
-    public void onBackPressed() {
-        OCFileListFragment listOfFiles = getListOfFilesFragment();
-        if (listOfFiles != null) {  // should never be null, indeed
-            int levelsUp = listOfFiles.onBrowseUp();
-            if (levelsUp == 0) {
-                finish();
-                return;
-            }
-            setFile(listOfFiles.getCurrentFile());
-            updateNavigationElementsInActionBar();
-        }
-    }
-
-    protected void updateNavigationElementsInActionBar() {
-        OCFile currentDir = getCurrentFolder();
-        ActionBar actionBar = getSupportActionBar();
-
-        if (actionBar != null) {
-            boolean atRoot = currentDir == null || currentDir.getParentId() == 0;
-            actionBar.setDisplayHomeAsUpEnabled(!atRoot);
-            actionBar.setHomeButtonEnabled(!atRoot);
-
-            themeToolbarUtils.tintBackButton(actionBar, this);
-
-            themeToolbarUtils.setColoredTitle(getSupportActionBar(), atRoot ? caption : currentDir.getFileName(), this);
-        }
-    }
-
-    /**
-     * Set per-view controllers
-     */
-    private void initControls() {
-        mCancelBtn = findViewById(R.id.folder_picker_btn_cancel);
-        mChooseBtn = findViewById(R.id.folder_picker_btn_choose);
-
-        if (mChooseBtn != null) {
-            themeButtonUtils.colorPrimaryButton(mChooseBtn, this, themeColorUtils);
-            mChooseBtn.setOnClickListener(this);
-        }
-
-        if (mCancelBtn != null) {
-            if (this instanceof FilePickerActivity) {
-                themeButtonUtils.colorPrimaryButton(mCancelBtn, this, themeColorUtils);
-            } else {
-                mCancelBtn.setTextColor(themeColorUtils.primaryColor(this, true));
-            }
-            mCancelBtn.setOnClickListener(this);
-        }
-    }
-
-    @Override
-    public void onClick(View v) {
-        if (v.equals(mCancelBtn)) {
-            finish();
-        } else if (v.equals(mChooseBtn)) {
-            Intent i = getIntent();
-            ArrayList<Parcelable> targetFiles = i.getParcelableArrayListExtra(FolderPickerActivity.EXTRA_FILES);
-
-            Intent data = new Intent();
-            data.putExtra(EXTRA_FOLDER, getListOfFilesFragment().getCurrentFile());
-            data.putParcelableArrayListExtra(EXTRA_FILES, targetFiles);
-            setResult(RESULT_OK, data);
-
-            finish();
-        }
-    }
-
-
-    @Override
-    public void onRemoteOperationFinish(RemoteOperation operation, RemoteOperationResult result) {
-        super.onRemoteOperationFinish(operation, result);
-
-        if (operation instanceof CreateFolderOperation) {
-            onCreateFolderOperationFinish((CreateFolderOperation) operation, result);
-
-        }
-    }
-
-
-    /**
-     * Updates the view associated to the activity after the finish of an operation trying to create a new folder.
-     *
-     * @param operation Creation operation performed.
-     * @param result    Result of the creation.
-     */
-    private void onCreateFolderOperationFinish(
-        CreateFolderOperation operation, RemoteOperationResult result
-    ) {
-
-        if (result.isSuccess()) {
-            OCFileListFragment fileListFragment = getListOfFilesFragment();
-            if (fileListFragment != null) {
-                fileListFragment.onItemClicked(getStorageManager().getFileByPath(operation.getRemotePath()));
-            }
-        } else {
-            try {
-                DisplayUtils.showSnackMessage(
-                    this, ErrorMessageAdapter.getErrorCauseMessage(result, operation, getResources())
-                                             );
-
-            } catch (NotFoundException e) {
-                Log_OC.e(TAG, "Error while trying to show fail message ", e);
-            }
-        }
-    }
-
-    public void search(String query) {
-        OCFileListFragment fileListFragment = getListOfFilesFragment();
-        if (fileListFragment != null) {
-            fileListFragment.onMessageEvent(new SearchEvent(query, SearchRemoteOperation.SearchType.FILE_SEARCH));
-        }
-    }
-
-    private class SyncBroadcastReceiver extends BroadcastReceiver {
-
-        /**
-         * {@link BroadcastReceiver} to enable syncing feedback in UI
-         */
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            try {
-                String event = intent.getAction();
-                Log_OC.d(TAG, "Received broadcast " + event);
-                String accountName = intent.getStringExtra(FileSyncAdapter.EXTRA_ACCOUNT_NAME);
-                String syncFolderRemotePath = intent.getStringExtra(FileSyncAdapter.EXTRA_FOLDER_PATH);
-                RemoteOperationResult syncResult = (RemoteOperationResult)
-                    DataHolderUtil.getInstance().retrieve(intent.getStringExtra(FileSyncAdapter.EXTRA_RESULT));
-                boolean sameAccount = getAccount() != null && accountName.equals(getAccount().name)
-                    && getStorageManager() != null;
-
-                if (sameAccount) {
-                    if (FileSyncAdapter.EVENT_FULL_SYNC_START.equals(event)) {
-                        mSyncInProgress = true;
-                    } else {
-                        OCFile currentFile = (getFile() == null) ? null :
-                            getStorageManager().getFileByPath(getFile().getRemotePath());
-                        OCFile currentDir = (getCurrentFolder() == null) ? null :
-                            getStorageManager().getFileByPath(getCurrentFolder().getRemotePath());
-
-                        if (currentDir == null) {
-                            // current folder was removed from the server
-                            DisplayUtils.showSnackMessage(getActivity(),
-                                                          R.string.sync_current_folder_was_removed,
-                                                          getCurrentFolder().getFileName());
-                            browseToRoot();
-                        } else {
-                            if (currentFile == null && !getFile().isFolder()) {
-                                // currently selected file was removed in the server, and now we know it
-                                currentFile = currentDir;
-                            }
-
-                            if (currentDir.getRemotePath().equals(syncFolderRemotePath)) {
-                                OCFileListFragment fileListFragment = getListOfFilesFragment();
-                                if (fileListFragment != null) {
-                                    fileListFragment.listDirectory(currentDir, false, false);
-                                }
-                            }
-                            setFile(currentFile);
-                        }
-
-                        mSyncInProgress = (!FileSyncAdapter.EVENT_FULL_SYNC_END.equals(event) &&
-                            !RefreshFolderOperation.EVENT_SINGLE_FOLDER_SHARES_SYNCED.equals(event));
-
-                        if (RefreshFolderOperation.EVENT_SINGLE_FOLDER_CONTENTS_SYNCED.equals(event) &&
-                            /// TODO refactor and make common
-                            syncResult != null && !syncResult.isSuccess()) {
-
-                            if (ResultCode.UNAUTHORIZED == syncResult.getCode() || (syncResult.isException()
-                                && syncResult.getException() instanceof AuthenticatorException)) {
-                                requestCredentialsUpdate(context);
-                            } else if (ResultCode.SSL_RECOVERABLE_PEER_UNVERIFIED == syncResult.getCode()) {
-                                showUntrustedCertDialog(syncResult);
-                            }
-
-                        }
-                    }
-                    DataHolderUtil.getInstance().delete(intent.getStringExtra(FileSyncAdapter.EXTRA_RESULT));
-                    Log_OC.d(TAG, "Setting progress visibility to " + mSyncInProgress);
-
-                    getListOfFilesFragment().setLoading(mSyncInProgress);
-
-                    setBackgroundText();
-                }
-
-            } catch (RuntimeException e) {
-                // avoid app crashes after changing the serial id of RemoteOperationResult
-                // in owncloud library with broadcast notifications pending to process
-                DataHolderUtil.getInstance().delete(intent.getStringExtra(FileSyncAdapter.EXTRA_RESULT));
-            }
-        }
-    }
-
-    @Override
-    public void showDetails(OCFile file) {
-        // not used at the moment
-    }
-
-    @Override
-    public void showDetails(OCFile file, int activeTab) {
-        // not used at the moment
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public void onTransferStateChanged(OCFile file, boolean downloading, boolean uploading) {
-        // not used at the moment
-    }
-
-    @Override
-    public void onRefresh() {
-        refreshList(true);
-    }
-
-    @Override
-    public void onRefresh(boolean enforced) {
-        refreshList(enforced);
-    }
-
-    private void refreshList(boolean ignoreETag) {
-        OCFileListFragment listOfFiles = getListOfFilesFragment();
-        if (listOfFiles != null) {
-            OCFile folder = listOfFiles.getCurrentFile();
-            if (folder != null) {
-                startSyncFolderOperation(folder, ignoreETag);
-            }
-        }
-    }
-
-    public boolean isDoNotEnterEncryptedFolder() {
-        return mDoNotEnterEncryptedFolder;
-    }
-
-    @Override
-    public void onSortingOrderChosen(FileSortOrder selection) {
-        getListOfFilesFragment().sortFiles(selection);
-    }
-}

+ 556 - 0
app/src/main/java/com/owncloud/android/ui/activity/FolderPickerActivity.kt

@@ -0,0 +1,556 @@
+/*
+ *   ownCloud Android client application
+ *
+ *   Copyright (C) 2016 ownCloud Inc.
+ *
+ *   This program is free software: you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License version 2,
+ *   as published by the Free Software Foundation.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+package com.owncloud.android.ui.activity
+
+import android.accounts.AuthenticatorException
+import android.app.Activity
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.content.res.Resources
+import android.os.Bundle
+import android.os.Parcelable
+import android.util.Log
+import android.view.ActionMode
+import android.view.Menu
+import android.view.MenuItem
+import android.view.View
+import androidx.localbroadcastmanager.content.LocalBroadcastManager
+import com.google.android.material.button.MaterialButton
+import com.nextcloud.client.di.Injectable
+import com.owncloud.android.R
+import com.owncloud.android.datamodel.OCFile
+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.SearchRemoteOperation
+import com.owncloud.android.operations.CreateFolderOperation
+import com.owncloud.android.operations.RefreshFolderOperation
+import com.owncloud.android.syncadapter.FileSyncAdapter
+import com.owncloud.android.ui.dialog.CreateFolderDialogFragment
+import com.owncloud.android.ui.dialog.SortingOrderDialogFragment.OnSortingOrderListener
+import com.owncloud.android.ui.events.SearchEvent
+import com.owncloud.android.ui.fragment.FileFragment
+import com.owncloud.android.ui.fragment.OCFileListFragment
+import com.owncloud.android.utils.DataHolderUtil
+import com.owncloud.android.utils.DisplayUtils
+import com.owncloud.android.utils.ErrorMessageAdapter
+import com.owncloud.android.utils.FileSortOrder
+import java.io.File
+import javax.inject.Inject
+
+@Suppress("Detekt.TooManyFunctions") // legacy code
+open class FolderPickerActivity :
+    FileActivity(),
+    FileFragment.ContainerActivity,
+    View.OnClickListener,
+    OnEnforceableRefreshListener,
+    Injectable,
+    OnSortingOrderListener {
+
+    private var mSyncBroadcastReceiver: SyncBroadcastReceiver? = null
+    private var mSyncInProgress = false
+    private var mSearchOnlyFolders = false
+    var isDoNotEnterEncryptedFolder = false
+        private set
+    private var mCancelBtn: MaterialButton? = null
+    private var mChooseBtn: MaterialButton? = null
+    private var caption: String? = null
+
+    @Inject
+    lateinit var localBroadcastManager: LocalBroadcastManager
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        Log_OC.d(TAG, "onCreate() start")
+        super.onCreate(savedInstanceState)
+        if (this is FilePickerActivity) {
+            setContentView(R.layout.files_picker)
+        } else {
+            setContentView(R.layout.files_folder_picker)
+        }
+
+        // sets callback listeners for UI elements
+        initControls()
+
+        // Action bar setup
+        setupToolbar()
+        findViewById<View>(R.id.sort_list_button_group).visibility =
+            View.VISIBLE
+        findViewById<View>(R.id.switch_grid_view_button).visibility =
+            View.GONE
+        if (intent.getStringExtra(EXTRA_ACTION) != null) {
+            when (intent.getStringExtra(EXTRA_ACTION)) {
+                MOVE -> {
+                    caption = resources.getText(R.string.move_to).toString()
+                    mSearchOnlyFolders = true
+                    isDoNotEnterEncryptedFolder = true
+                }
+                COPY -> {
+                    caption = resources.getText(R.string.copy_to).toString()
+                    mSearchOnlyFolders = true
+                    isDoNotEnterEncryptedFolder = true
+                }
+                else -> caption = themeUtils.getDefaultDisplayNameForRootFolder(this)
+            }
+        } else {
+            caption = themeUtils.getDefaultDisplayNameForRootFolder(this)
+        }
+        if (intent.getParcelableExtra<Parcelable?>(EXTRA_CURRENT_FOLDER) != null) {
+            file = intent.getParcelableExtra(EXTRA_CURRENT_FOLDER)
+        }
+        if (savedInstanceState == null) {
+            createFragments()
+        }
+        updateActionBarTitleAndHomeButtonByString(caption)
+
+        // always AFTER setContentView(...) ; to work around bug in its implementation
+
+        // sets message for empty list of folders
+        setBackgroundText()
+        Log_OC.d(TAG, "onCreate() end")
+    }
+
+    override fun onActionModeStarted(mode: ActionMode) {
+        super.onActionModeStarted(mode)
+        if (account != null) {
+            updateFileFromDB()
+            var folder = file
+            if (folder == null || !folder.isFolder) {
+                // fall back to root folder
+                file = storageManager.getFileByPath(OCFile.ROOT_PATH)
+                folder = file
+            }
+            val listOfFolders = listOfFilesFragment
+            listOfFolders!!.listDirectory(folder, false, false)
+            startSyncFolderOperation(folder, false)
+            updateNavigationElementsInActionBar()
+        }
+    }
+
+    private val activity: Activity
+        get() = this
+
+    protected open fun createFragments() {
+        val listOfFiles = OCFileListFragment()
+        val args = Bundle()
+        args.putBoolean(OCFileListFragment.ARG_ONLY_FOLDERS_CLICKABLE, true)
+        args.putBoolean(OCFileListFragment.ARG_HIDE_FAB, true)
+        args.putBoolean(OCFileListFragment.ARG_HIDE_ITEM_OPTIONS, true)
+        args.putBoolean(OCFileListFragment.ARG_SEARCH_ONLY_FOLDER, mSearchOnlyFolders)
+        listOfFiles.arguments = args
+        val transaction = supportFragmentManager.beginTransaction()
+        transaction.add(R.id.fragment_container, listOfFiles, TAG_LIST_OF_FOLDERS)
+        transaction.commit()
+    }
+
+    /**
+     * Show a text message on screen view for notifying user if content is loading or folder is empty
+     */
+    private fun setBackgroundText() {
+        val listFragment = listOfFilesFragment
+        if (listFragment != null) {
+            if (!mSyncInProgress) {
+                listFragment.setMessageForEmptyList(
+                    R.string.file_list_empty_headline,
+                    R.string.file_list_empty_moving,
+                    R.drawable.ic_list_empty_create_folder,
+                    true
+                )
+            } else {
+                listFragment.setEmptyListLoadingMessage()
+            }
+        } else {
+            Log.e(TAG, "OCFileListFragment is null")
+        }
+    }
+
+    protected val listOfFilesFragment: OCFileListFragment?
+        protected get() {
+            val listOfFiles = supportFragmentManager.findFragmentByTag(TAG_LIST_OF_FOLDERS)
+            if (listOfFiles != null) {
+                return listOfFiles as OCFileListFragment?
+            }
+            Log_OC.e(TAG, "Access to non existing list of files fragment!!")
+            return null
+        }
+
+    /**
+     * {@inheritDoc}
+     *
+     *
+     * Updates action bar and second fragment, if in dual pane mode.
+     */
+    override fun onBrowsedDownTo(directory: OCFile) {
+        file = directory
+        updateNavigationElementsInActionBar()
+        // Sync Folder
+        startSyncFolderOperation(directory, false)
+    }
+
+    override fun onSavedCertificate() {
+        startSyncFolderOperation(currentDir, false)
+    }
+
+    private fun startSyncFolderOperation(folder: OCFile?, ignoreETag: Boolean) {
+        val currentSyncTime = System.currentTimeMillis()
+        mSyncInProgress = true
+
+        // perform folder synchronization
+        val refreshFolderOperation: RemoteOperation<*> = RefreshFolderOperation(
+            folder,
+            currentSyncTime,
+            false,
+            ignoreETag,
+            storageManager,
+            user.orElseThrow { RuntimeException("User not set") },
+            applicationContext
+        )
+        refreshFolderOperation.execute(account, this, null, null)
+        listOfFilesFragment!!.isLoading = true
+        setBackgroundText()
+    }
+
+    override fun onResume() {
+        super.onResume()
+        Log_OC.e(TAG, "onResume() start")
+        listOfFilesFragment!!.isLoading = mSyncInProgress
+
+        // refresh list of files
+        refreshListOfFilesFragment(false)
+
+        // Listen for sync messages
+        val syncIntentFilter = IntentFilter(FileSyncAdapter.EVENT_FULL_SYNC_START)
+        syncIntentFilter.addAction(FileSyncAdapter.EVENT_FULL_SYNC_END)
+        syncIntentFilter.addAction(FileSyncAdapter.EVENT_FULL_SYNC_FOLDER_CONTENTS_SYNCED)
+        syncIntentFilter.addAction(RefreshFolderOperation.EVENT_SINGLE_FOLDER_CONTENTS_SYNCED)
+        syncIntentFilter.addAction(RefreshFolderOperation.EVENT_SINGLE_FOLDER_SHARES_SYNCED)
+        mSyncBroadcastReceiver = SyncBroadcastReceiver()
+        localBroadcastManager.registerReceiver(mSyncBroadcastReceiver!!, syncIntentFilter)
+        Log_OC.d(TAG, "onResume() end")
+    }
+
+    override fun onPause() {
+        Log_OC.e(TAG, "onPause() start")
+        if (mSyncBroadcastReceiver != null) {
+            localBroadcastManager.unregisterReceiver(mSyncBroadcastReceiver!!)
+            mSyncBroadcastReceiver = null
+        }
+        Log_OC.d(TAG, "onPause() end")
+        super.onPause()
+    }
+
+    override fun onCreateOptionsMenu(menu: Menu): Boolean {
+        val inflater = menuInflater
+        inflater.inflate(R.menu.activity_folder_picker, menu)
+        return true
+    }
+
+    override fun onOptionsItemSelected(item: MenuItem): Boolean {
+        var retval = true
+        val itemId = item.itemId
+        if (itemId == R.id.action_create_dir) {
+            val dialog = CreateFolderDialogFragment.newInstance(currentFolder)
+            dialog.show(supportFragmentManager, CreateFolderDialogFragment.CREATE_FOLDER_FRAGMENT)
+        } else if (itemId == android.R.id.home) {
+            val currentDir = currentFolder
+            if (currentDir != null && currentDir.parentId != 0L) {
+                onBackPressed()
+            }
+        } else {
+            retval = super.onOptionsItemSelected(item)
+        }
+        return retval
+    }
+
+    // If the file is null, take the root folder to avoid any error in functions depending on this one
+    val currentFolder: OCFile?
+        get() {
+            val currentFile = file
+            var finalFolder: OCFile? = null
+            val storageManager = storageManager
+
+            // If the file is null, take the root folder to avoid any error in functions depending on this one
+            if (currentFile != null) {
+                if (currentFile.isFolder) {
+                    finalFolder = currentFile
+                } else if (currentFile.remotePath != null) {
+                    val parentPath = File(currentFile.remotePath).parent
+                    finalFolder = storageManager.getFileByPath(parentPath)
+                }
+            } else {
+                finalFolder = storageManager.getFileByPath(OCFile.ROOT_PATH)
+            }
+            return finalFolder
+        }
+
+    private fun refreshListOfFilesFragment(fromSearch: Boolean) {
+        val fileListFragment = listOfFilesFragment
+        fileListFragment?.listDirectory(false, fromSearch)
+    }
+
+    fun browseToRoot() {
+        val listOfFiles = listOfFilesFragment
+        if (listOfFiles != null) { // should never be null, indeed
+            val root = storageManager.getFileByPath(OCFile.ROOT_PATH)
+            listOfFiles.listDirectory(root, false, false)
+            file = listOfFiles.currentFile
+            updateNavigationElementsInActionBar()
+            startSyncFolderOperation(root, false)
+        }
+    }
+
+    override fun onBackPressed() {
+        val listOfFiles = listOfFilesFragment
+        if (listOfFiles != null) { // should never be null, indeed
+            val levelsUp = listOfFiles.onBrowseUp()
+            if (levelsUp == 0) {
+                finish()
+                return
+            }
+            file = listOfFiles.currentFile
+            updateNavigationElementsInActionBar()
+        }
+    }
+
+    private fun updateNavigationElementsInActionBar() {
+        val currentDir = currentFolder
+        val actionBar = supportActionBar
+        if (actionBar != null) {
+            val atRoot = currentDir == null || currentDir.parentId == 0L
+            actionBar.setDisplayHomeAsUpEnabled(!atRoot)
+            actionBar.setHomeButtonEnabled(!atRoot)
+            themeToolbarUtils.tintBackButton(actionBar, this)
+            themeToolbarUtils.setColoredTitle(supportActionBar, if (atRoot) caption else currentDir!!.fileName, this)
+        }
+    }
+
+    /**
+     * Set per-view controllers
+     */
+    private fun initControls() {
+        mCancelBtn = findViewById(R.id.folder_picker_btn_cancel)
+        mChooseBtn = findViewById(R.id.folder_picker_btn_choose)
+        if (mChooseBtn != null) {
+            themeButtonUtils.colorPrimaryButton(mChooseBtn, this, themeColorUtils)
+            mChooseBtn!!.setOnClickListener(this)
+        }
+        if (mCancelBtn != null) {
+            if (this is FilePickerActivity) {
+                themeButtonUtils.colorPrimaryButton(mCancelBtn, this, themeColorUtils)
+            } else {
+                mCancelBtn!!.setTextColor(themeColorUtils.primaryColor(this, true))
+            }
+            mCancelBtn!!.setOnClickListener(this)
+        }
+    }
+
+    override fun onClick(v: View) {
+        if (v == mCancelBtn) {
+            finish()
+        } else if (v == mChooseBtn) {
+            val i = intent
+            val resultData = Intent()
+            resultData.putExtra(EXTRA_FOLDER, listOfFilesFragment!!.currentFile)
+            val targetFiles = i.getParcelableArrayListExtra<Parcelable>(EXTRA_FILES)
+            if (targetFiles != null) {
+                resultData.putParcelableArrayListExtra(EXTRA_FILES, targetFiles)
+            }
+            val targetFilePaths = i.getStringArrayListExtra(EXTRA_FILE_PATHS)
+            if (targetFilePaths != null) {
+                resultData.putStringArrayListExtra(EXTRA_FILE_PATHS, targetFilePaths)
+            }
+            setResult(RESULT_OK, resultData)
+            finish()
+        }
+    }
+
+    override fun onRemoteOperationFinish(operation: RemoteOperation<*>?, result: RemoteOperationResult<*>) {
+        super.onRemoteOperationFinish(operation, result)
+        if (operation is CreateFolderOperation) {
+            onCreateFolderOperationFinish(operation, result)
+        }
+    }
+
+    /**
+     * Updates the view associated to the activity after the finish of an operation trying to create a new folder.
+     *
+     * @param operation Creation operation performed.
+     * @param result    Result of the creation.
+     */
+    private fun onCreateFolderOperationFinish(
+        operation: CreateFolderOperation,
+        result: RemoteOperationResult<*>
+    ) {
+        if (result.isSuccess) {
+            val fileListFragment = listOfFilesFragment
+            fileListFragment?.onItemClicked(storageManager.getFileByPath(operation.remotePath))
+        } else {
+            try {
+                DisplayUtils.showSnackMessage(
+                    this, ErrorMessageAdapter.getErrorCauseMessage(result, operation, resources)
+                )
+            } catch (e: Resources.NotFoundException) {
+                Log_OC.e(TAG, "Error while trying to show fail message ", e)
+            }
+        }
+    }
+
+    fun search(query: String?) {
+        val fileListFragment = listOfFilesFragment
+        fileListFragment?.onMessageEvent(
+            SearchEvent(
+                query!!,
+                SearchRemoteOperation.SearchType.FILE_SEARCH
+            )
+        )
+    }
+
+    private inner class SyncBroadcastReceiver : BroadcastReceiver() {
+        /**
+         * [BroadcastReceiver] to enable syncing feedback in UI
+         */
+        @Suppress("Detekt.ComplexMethod", "Detekt.NestedBlockDepth", "Detekt.TooGenericExceptionCaught") // legacy code
+        override fun onReceive(context: Context, intent: Intent) {
+            try {
+                val event = intent.action
+                Log_OC.d(TAG, "Received broadcast $event")
+                val accountName = intent.getStringExtra(FileSyncAdapter.EXTRA_ACCOUNT_NAME)
+                val syncFolderRemotePath = intent.getStringExtra(FileSyncAdapter.EXTRA_FOLDER_PATH)
+                val syncResult = DataHolderUtil.getInstance()
+                    .retrieve(intent.getStringExtra(FileSyncAdapter.EXTRA_RESULT)) as RemoteOperationResult<*>
+                val sameAccount = account != null && accountName == account.name && storageManager != null
+                if (sameAccount) {
+                    if (FileSyncAdapter.EVENT_FULL_SYNC_START == event) {
+                        mSyncInProgress = true
+                    } else {
+                        var currentFile = if (file == null) null else storageManager.getFileByPath(file.remotePath)
+                        val currentDir = if (currentFolder == null) null else storageManager.getFileByPath(
+                            currentFolder!!.remotePath
+                        )
+                        if (currentDir == null) {
+                            // current folder was removed from the server
+                            DisplayUtils.showSnackMessage(
+                                activity,
+                                R.string.sync_current_folder_was_removed,
+                                currentFolder!!.fileName
+                            )
+                            browseToRoot()
+                        } else {
+                            if (currentFile == null && !file.isFolder) {
+                                // currently selected file was removed in the server, and now we know it
+                                currentFile = currentDir
+                            }
+                            if (currentDir.remotePath == syncFolderRemotePath) {
+                                val fileListFragment = listOfFilesFragment
+                                fileListFragment?.listDirectory(currentDir, false, false)
+                            }
+                            file = currentFile
+                        }
+                        mSyncInProgress = FileSyncAdapter.EVENT_FULL_SYNC_END != event &&
+                            RefreshFolderOperation.EVENT_SINGLE_FOLDER_SHARES_SYNCED != event
+                        if (RefreshFolderOperation.EVENT_SINGLE_FOLDER_CONTENTS_SYNCED == event && !syncResult.isSuccess
+                        ) {
+                            if (ResultCode.UNAUTHORIZED == syncResult.code || (
+                                syncResult.isException &&
+                                    syncResult.exception is AuthenticatorException
+                                )
+                            ) {
+                                requestCredentialsUpdate(context)
+                            } else if (ResultCode.SSL_RECOVERABLE_PEER_UNVERIFIED == syncResult.code) {
+                                showUntrustedCertDialog(syncResult)
+                            }
+                        }
+                    }
+                    DataHolderUtil.getInstance().delete(intent.getStringExtra(FileSyncAdapter.EXTRA_RESULT))
+                    Log_OC.d(TAG, "Setting progress visibility to $mSyncInProgress")
+                    listOfFilesFragment!!.isLoading = mSyncInProgress
+                    setBackgroundText()
+                }
+            } catch (e: RuntimeException) {
+                Log_OC.e(TAG, "Error on broadcast receiver", e)
+                // avoid app crashes after changing the serial id of RemoteOperationResult
+                // in owncloud library with broadcast notifications pending to process
+                DataHolderUtil.getInstance().delete(intent.getStringExtra(FileSyncAdapter.EXTRA_RESULT))
+            }
+        }
+    }
+
+    override fun showDetails(file: OCFile) {
+        // not used at the moment
+    }
+
+    override fun showDetails(file: OCFile, activeTab: Int) {
+        // not used at the moment
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    override fun onTransferStateChanged(file: OCFile, downloading: Boolean, uploading: Boolean) {
+        // not used at the moment
+    }
+
+    override fun onRefresh() {
+        refreshList(true)
+    }
+
+    override fun onRefresh(enforced: Boolean) {
+        refreshList(enforced)
+    }
+
+    private fun refreshList(ignoreETag: Boolean) {
+        val listOfFiles = listOfFilesFragment
+        if (listOfFiles != null) {
+            val folder = listOfFiles.currentFile
+            folder?.let { startSyncFolderOperation(it, ignoreETag) }
+        }
+    }
+
+    override fun onSortingOrderChosen(selection: FileSortOrder) {
+        listOfFilesFragment!!.sortFiles(selection)
+    }
+
+    companion object {
+        @JvmField
+        val EXTRA_FOLDER = FolderPickerActivity::class.java.canonicalName?.plus(".EXTRA_FOLDER")
+
+        @JvmField
+        @Deprecated(
+            """This leads to crashes when too many files are passed. Use EXTRA_FILE_PATHS instead, or
+      better yet, store the target files wherever you need to use them instead of passing them through this activity."""
+        )
+        val EXTRA_FILES = FolderPickerActivity::class.java.canonicalName?.plus(".EXTRA_FILES")
+
+        @JvmField
+        val EXTRA_FILE_PATHS = FolderPickerActivity::class.java.canonicalName?.plus(".EXTRA_FILE_PATHS")
+
+        @JvmField
+        val EXTRA_ACTION = FolderPickerActivity::class.java.canonicalName?.plus(".EXTRA_ACTION")
+
+        @JvmField
+        val EXTRA_CURRENT_FOLDER = FolderPickerActivity::class.java.canonicalName?.plus(".EXTRA_CURRENT_FOLDER")
+
+        const val MOVE = "MOVE"
+        const val COPY = "COPY"
+        private val TAG = FolderPickerActivity::class.java.simpleName
+        protected const val TAG_LIST_OF_FOLDERS = "LIST_OF_FOLDERS"
+    }
+}

+ 28 - 12
app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java

@@ -1190,18 +1190,10 @@ public class OCFileListFragment extends ExtendedListFragment implements
             mContainerActivity.getFileOperationsHelper().toggleFavoriteFiles(checkedFiles, false);
             return true;
         } else if (itemId == R.id.action_move) {
-            Intent action = new Intent(getActivity(), FolderPickerActivity.class);
-            action.putParcelableArrayListExtra(FolderPickerActivity.EXTRA_FILES, new ArrayList<>(checkedFiles));
-            action.putExtra(FolderPickerActivity.EXTRA_CURRENT_FOLDER, mFile);
-            action.putExtra(FolderPickerActivity.EXTRA_ACTION, FolderPickerActivity.MOVE);
-            getActivity().startActivityForResult(action, FileDisplayActivity.REQUEST_CODE__MOVE_FILES);
+            pickFolderForMoveOrCopy(FolderPickerActivity.MOVE, checkedFiles);
             return true;
         } else if (itemId == R.id.action_copy) {
-            Intent action = new Intent(getActivity(), FolderPickerActivity.class);
-            action.putParcelableArrayListExtra(FolderPickerActivity.EXTRA_FILES, new ArrayList<>(checkedFiles));
-            action.putExtra(FolderPickerActivity.EXTRA_CURRENT_FOLDER, mFile);
-            action.putExtra(FolderPickerActivity.EXTRA_ACTION, FolderPickerActivity.COPY);
-            getActivity().startActivityForResult(action, FileDisplayActivity.REQUEST_CODE__COPY_FILES);
+            pickFolderForMoveOrCopy(FolderPickerActivity.COPY, checkedFiles);
             return true;
         } else if (itemId == R.id.action_select_all_action_menu) {
             selectAllFiles(true);
@@ -1219,9 +1211,33 @@ public class OCFileListFragment extends ExtendedListFragment implements
         return false;
     }
 
+    private void pickFolderForMoveOrCopy(final String extraAction, final Set<OCFile> checkedFiles) {
+        int requestCode;
+        switch (extraAction) {
+            case FolderPickerActivity.MOVE:
+                requestCode = FileDisplayActivity.REQUEST_CODE__MOVE_FILES;
+                break;
+            case FolderPickerActivity.COPY:
+                requestCode = FileDisplayActivity.REQUEST_CODE__COPY_FILES;
+                break;
+            default:
+                throw new IllegalArgumentException("Unknown extra action: " + extraAction);
+        }
+
+        final Intent action = new Intent(getActivity(), FolderPickerActivity.class);
+        final ArrayList<String> paths = new ArrayList<>(checkedFiles.size());
+        for (OCFile file : checkedFiles) {
+            paths.add(file.getRemotePath());
+        }
+        action.putStringArrayListExtra(FolderPickerActivity.EXTRA_FILE_PATHS, paths);
+        action.putExtra(FolderPickerActivity.EXTRA_CURRENT_FOLDER, mFile);
+        action.putExtra(FolderPickerActivity.EXTRA_ACTION, extraAction);
+        getActivity().startActivityForResult(action, requestCode);
+    }
+
+
     /**
-     * Use this to query the {@link OCFile} that is currently
-     * being displayed by this fragment
+     * Use this to query the {@link OCFile} that is currently being displayed by this fragment
      *
      * @return The currently viewed OCFile
      */

+ 12 - 16
app/src/main/java/com/owncloud/android/ui/helpers/FileOperationsHelper.java

@@ -1004,33 +1004,29 @@ public class FileOperationsHelper {
     /**
      * Start operations to move one or several files
      *
-     * @param files        Files to move
+     * @param filePaths    Remote paths of files to move
      * @param targetFolder Folder where the files while be moved into
      */
-    public void moveFiles(Collection<OCFile> files, OCFile targetFolder) {
-        for (OCFile file : files) {
-            Intent service = new Intent(fileActivity, OperationsService.class);
-            service.setAction(OperationsService.ACTION_MOVE_FILE);
-            service.putExtra(OperationsService.EXTRA_NEW_PARENT_PATH, targetFolder.getRemotePath());
-            service.putExtra(OperationsService.EXTRA_REMOTE_PATH, file.getRemotePath());
-            service.putExtra(OperationsService.EXTRA_ACCOUNT, fileActivity.getAccount());
-            mWaitingForOpId = fileActivity.getOperationsServiceBinder().queueNewOperation(service);
-        }
-        fileActivity.showLoadingDialog(fileActivity.getString(R.string.wait_a_moment));
+    public void moveFiles(final List<String> filePaths, final OCFile targetFolder) {
+        copyOrMoveFiles(OperationsService.ACTION_MOVE_FILE, filePaths, targetFolder);
     }
 
     /**
      * Start operations to copy one or several files
      *
-     * @param files        Files to copy
+     * @param filePaths    Remote paths of files to move
      * @param targetFolder Folder where the files while be copied into
      */
-    public void copyFiles(Collection<OCFile> files, OCFile targetFolder) {
-        for (OCFile file : files) {
+    public void copyFiles(final List<String> filePaths, final OCFile targetFolder) {
+        copyOrMoveFiles(OperationsService.ACTION_COPY_FILE, filePaths, targetFolder);
+    }
+
+    private void copyOrMoveFiles(final String action, final List<String> filePaths, final OCFile targetFolder) {
+        for (String path : filePaths) {
             Intent service = new Intent(fileActivity, OperationsService.class);
-            service.setAction(OperationsService.ACTION_COPY_FILE);
+            service.setAction(action);
             service.putExtra(OperationsService.EXTRA_NEW_PARENT_PATH, targetFolder.getRemotePath());
-            service.putExtra(OperationsService.EXTRA_REMOTE_PATH, file.getRemotePath());
+            service.putExtra(OperationsService.EXTRA_REMOTE_PATH, path);
             service.putExtra(OperationsService.EXTRA_ACCOUNT, fileActivity.getAccount());
             mWaitingForOpId = fileActivity.getOperationsServiceBinder().queueNewOperation(service);
         }