Răsfoiți Sursa

Merge pull request #1643 from owncloud/uploads_from_content_uris

Fixed uploads from other apps
David A. Velasco 9 ani în urmă
părinte
comite
3cf03eceaf

+ 1 - 1
AndroidManifest.xml

@@ -71,7 +71,7 @@
             </intent-filter>
         </activity>
         <activity android:name=".ui.activity.UploadFilesActivity" />
-        <activity android:name=".ui.activity.Uploader"
+        <activity android:name=".ui.activity.ReceiveExternalFilesActivity"
                   android:taskAffinity=""
                   android:excludeFromRecents="true">
             <intent-filter>

+ 43 - 86
src/com/owncloud/android/ui/activity/FileDisplayActivity.java

@@ -37,15 +37,14 @@ import android.content.SharedPreferences;
 import android.content.SyncRequest;
 import android.content.pm.PackageManager;
 import android.content.res.Resources.NotFoundException;
-import android.database.Cursor;
-import android.net.Uri;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.IBinder;
+import android.os.Parcelable;
 import android.preference.PreferenceManager;
-import android.provider.OpenableColumns;
 import android.support.design.widget.Snackbar;
 import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentManager;
 import android.support.v4.app.FragmentTransaction;
 import android.support.v4.content.ContextCompat;
 import android.support.v4.view.GravityCompat;
@@ -82,6 +81,8 @@ import com.owncloud.android.syncadapter.FileSyncAdapter;
 import com.owncloud.android.ui.fragment.FileDetailFragment;
 import com.owncloud.android.ui.fragment.FileFragment;
 import com.owncloud.android.ui.fragment.OCFileListFragment;
+import com.owncloud.android.ui.fragment.TaskRetainerFragment;
+import com.owncloud.android.ui.helpers.UriUploader;
 import com.owncloud.android.ui.preview.PreviewImageActivity;
 import com.owncloud.android.ui.preview.PreviewImageFragment;
 import com.owncloud.android.ui.preview.PreviewMediaFragment;
@@ -91,9 +92,9 @@ import com.owncloud.android.utils.DisplayUtils;
 import com.owncloud.android.utils.ErrorMessageAdapter;
 import com.owncloud.android.utils.FileStorageUtils;
 import com.owncloud.android.utils.PermissionUtil;
-import com.owncloud.android.utils.UriUtils;
 
 import java.io.File;
+import java.util.ArrayList;
 
 /**
  * Displays, what files the user has available in his ownCloud. This is the main view.
@@ -120,7 +121,7 @@ public class FileDisplayActivity extends HookActivity
     public static final String ACTION_DETAILS = "com.owncloud.android.ui.activity.action.DETAILS";
 
     public static final int REQUEST_CODE__SELECT_CONTENT_FROM_APPS = REQUEST_CODE__LAST_SHARED + 1;
-    public static final int REQUEST_CODE__SELECT_MULTIPLE_FILES = REQUEST_CODE__LAST_SHARED + 2;
+    public static final int REQUEST_CODE__SELECT_FILES_FROM_FILE_SYSTEM = REQUEST_CODE__LAST_SHARED + 2;
     public static final int REQUEST_CODE__MOVE_FILES = REQUEST_CODE__LAST_SHARED + 3;
     public static final int REQUEST_CODE__COPY_FILES = REQUEST_CODE__LAST_SHARED + 4;
 
@@ -188,6 +189,16 @@ public class FileDisplayActivity extends HookActivity
         //getSupportActionBar().setDisplayHomeAsUpEnabled(true);
         getSupportActionBar().setHomeButtonEnabled(true);
 
+        // Init Fragment without UI to retain AsyncTask across configuration changes
+        FragmentManager fm = getSupportFragmentManager();
+        TaskRetainerFragment taskRetainerFragment =
+                (TaskRetainerFragment) fm.findFragmentByTag(TaskRetainerFragment.FTAG_TASK_RETAINER_FRAGMENT);
+        if (taskRetainerFragment == null) {
+            taskRetainerFragment = new TaskRetainerFragment();
+            fm.beginTransaction()
+                    .add(taskRetainerFragment, TaskRetainerFragment.FTAG_TASK_RETAINER_FRAGMENT).commit();
+        }   // else, Fragment already created and retained across configuration change
+
         Log_OC.v(TAG, "onCreate() end");
     }
 
@@ -639,26 +650,15 @@ public class FileDisplayActivity extends HookActivity
     @Override
     protected void onActivityResult(int requestCode, int resultCode, Intent data) {
 
-        if (requestCode == REQUEST_CODE__SELECT_CONTENT_FROM_APPS && (resultCode == RESULT_OK ||
-                resultCode == UploadFilesActivity.RESULT_OK_AND_MOVE)) {
+        if (requestCode == REQUEST_CODE__SELECT_CONTENT_FROM_APPS &&
+            (resultCode == RESULT_OK || resultCode == UploadFilesActivity.RESULT_OK_AND_MOVE)) {
 
-            //getClipData is only supported on api level 16+, Jelly Bean
-            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN &&
-                    data.getClipData() != null &&
-                    data.getClipData().getItemCount() > 0) {
+            requestUploadOfContentFromApps(data, resultCode);
 
-                for (int i = 0; i < data.getClipData().getItemCount(); i++) {
-                    Intent intent = new Intent();
-                    intent.setData(data.getClipData().getItemAt(i).getUri());
-                    requestSimpleUpload(intent, resultCode);
-                }
+        } else if (requestCode == REQUEST_CODE__SELECT_FILES_FROM_FILE_SYSTEM &&
+            (resultCode == RESULT_OK || resultCode == UploadFilesActivity.RESULT_OK_AND_MOVE)) {
 
-            } else {
-                requestSimpleUpload(data, resultCode);
-            }
-        } else if (requestCode == REQUEST_CODE__SELECT_MULTIPLE_FILES && (resultCode == RESULT_OK ||
-                resultCode == UploadFilesActivity.RESULT_OK_AND_MOVE)) {
-            requestMultipleUpload(data, resultCode);
+            requestUploadOfFilesFromFileSystem(data, resultCode);
 
         } else if (requestCode == REQUEST_CODE__MOVE_FILES && resultCode == RESULT_OK) {
             final Intent fData = data;
@@ -693,7 +693,7 @@ public class FileDisplayActivity extends HookActivity
 
     }
 
-    private void requestMultipleUpload(Intent data, int resultCode) {
+    private void requestUploadOfFilesFromFileSystem(Intent data, int resultCode) {
         String[] filePaths = data.getStringArrayExtra(UploadFilesActivity.EXTRA_CHOSEN_FILES);
         if (filePaths != null) {
             String[] remotePaths = new String[filePaths.length];
@@ -726,84 +726,41 @@ public class FileDisplayActivity extends HookActivity
     }
 
 
-    private void requestSimpleUpload(Intent data, int resultCode) {
-        String filePath = null;
-        String mimeType = null;
-
-        Uri selectedImageUri = data.getData();
-
-        try {
-            mimeType = getContentResolver().getType(selectedImageUri);
+    private void requestUploadOfContentFromApps(Intent contentIntent, int resultCode) {
 
-            String fileManagerString = selectedImageUri.getPath();
-            String selectedImagePath = UriUtils.getLocalPath(selectedImageUri, this);
+        ArrayList<Parcelable> streamsToUpload = new ArrayList<>();
 
-            if (selectedImagePath != null)
-                filePath = selectedImagePath;
-            else
-                filePath = fileManagerString;
+        //getClipData is only supported on api level 16+, Jelly Bean
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN &&
+            contentIntent.getClipData() != null &&
+            contentIntent.getClipData().getItemCount() > 0) {
 
-        } catch (Exception e) {
-            Log_OC.e(TAG, "Unexpected exception when trying to read the result of " +
-                    "Intent.ACTION_GET_CONTENT", e);
-
-        } finally {
-            if (filePath == null) {
-                Log_OC.e(TAG, "Couldn't resolve path to file");
-                Toast t = Toast.makeText(
-                        this, getString(R.string.filedisplay_unexpected_bad_get_content),
-                        Toast.LENGTH_LONG
-                );
-                t.show();
-                return;
+            for (int i = 0; i < contentIntent.getClipData().getItemCount(); i++) {
+                streamsToUpload.add(contentIntent.getClipData().getItemAt(i).getUri());
             }
-        }
-
-        Intent i = new Intent(this, FileUploader.class);
-        i.putExtra(FileUploader.KEY_ACCOUNT,
-                getAccount());
-        OCFile currentDir = getCurrentDir();
-        String remotePath = (currentDir != null) ? currentDir.getRemotePath() : OCFile.ROOT_PATH;
-
-        if (selectedImageUri.toString().startsWith(UriUtils.URI_CONTENT_SCHEME)) {
-//            Cursor cursor = getContentResolver().query(Uri.parse(filePath), null, null, null, null);
-//            try {
-//                if (cursor != null && cursor.moveToFirst()) {
-//                    String displayName = cursor.getString(cursor.getColumnIndex(
-//                            OpenableColumns.DISPLAY_NAME));
-//                    Log_OC.v(TAG, "Display Name: " + displayName);
-//
-//                    displayName.replace(File.separatorChar, '_');
-//                    displayName.replace(File.pathSeparatorChar, '_');
-//                    remotePath += displayName + DisplayUtils.getComposedFileExtension(filePath);
-//
-//                }
-//                // and what happens in case of error?; wrong target name for the upload
-//            } finally {
-//                cursor.close();
-//            }
-            // Pending to be fixed
-            Toast.makeText(this, R.string.common_error_unknown, Toast.LENGTH_SHORT).show();
-            return;
 
         } else {
-            remotePath += new File(filePath).getName();
+            streamsToUpload.add(contentIntent.getData());
         }
 
         int behaviour = (resultCode == UploadFilesActivity.RESULT_OK_AND_MOVE) ? FileUploader.LOCAL_BEHAVIOUR_MOVE :
                 FileUploader.LOCAL_BEHAVIOUR_COPY;
-        FileUploader.UploadRequester requester = new FileUploader.UploadRequester();
-        requester.uploadNewFile(
+
+        OCFile currentDir = getCurrentDir();
+        String remotePath = (currentDir != null) ? currentDir.getRemotePath() : OCFile.ROOT_PATH;
+
+        UriUploader uploader = new UriUploader(
                 this,
-                getAccount(),
-                filePath,
+                streamsToUpload,
                 remotePath,
+                getAccount(),
                 behaviour,
-                mimeType,
-                false,          // do not create parent folder if not existent
-                UploadFileOperation.CREATED_BY_USER
+                false, // Not show waiting dialog while file is being copied from private storage
+                null  // Not needed copy temp task listener
         );
 
+        uploader.uploadUris();
+
     }
 
     /**

+ 45 - 204
src/com/owncloud/android/ui/activity/Uploader.java → src/com/owncloud/android/ui/activity/ReceiveExternalFilesActivity.java

@@ -29,7 +29,6 @@ import android.accounts.AuthenticatorException;
 import android.annotation.SuppressLint;
 import android.app.Dialog;
 import android.content.BroadcastReceiver;
-import android.content.ContentResolver;
 import android.content.Context;
 import android.content.DialogInterface;
 import android.content.IntentFilter;
@@ -37,12 +36,9 @@ import android.content.DialogInterface.OnCancelListener;
 import android.content.DialogInterface.OnClickListener;
 import android.content.Intent;
 import android.content.res.Resources.NotFoundException;
-import android.net.Uri;
 import android.os.Bundle;
 import android.os.Parcelable;
-import android.support.v4.app.Fragment;
 import android.support.v4.app.FragmentManager;
-import android.support.v4.app.FragmentTransaction;
 import android.support.v7.app.ActionBar;
 import android.support.v7.app.AlertDialog;
 import android.support.v7.app.AlertDialog.Builder;
@@ -68,16 +64,15 @@ import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCo
 import com.owncloud.android.lib.common.utils.Log_OC;
 import com.owncloud.android.operations.CreateFolderOperation;
 import com.owncloud.android.operations.RefreshFolderOperation;
-import com.owncloud.android.operations.UploadFileOperation;
 import com.owncloud.android.syncadapter.FileSyncAdapter;
 import com.owncloud.android.ui.adapter.UploaderAdapter;
 import com.owncloud.android.ui.dialog.ConfirmationDialogFragment;
 import com.owncloud.android.ui.dialog.CreateFolderDialogFragment;
-import com.owncloud.android.ui.dialog.LoadingDialog;
 import com.owncloud.android.ui.asynctasks.CopyAndUploadContentUrisTask;
+import com.owncloud.android.ui.fragment.TaskRetainerFragment;
+import com.owncloud.android.ui.helpers.UriUploader;
 import com.owncloud.android.utils.DisplayUtils;
 import com.owncloud.android.utils.ErrorMessageAdapter;
-import com.owncloud.android.utils.UriUtils;
 
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -90,13 +85,11 @@ import java.util.Vector;
 /**
  * This can be used to upload things to an ownCloud instance.
  */
-public class Uploader extends FileActivity
+public class ReceiveExternalFilesActivity extends FileActivity
         implements OnItemClickListener, android.view.View.OnClickListener,
     CopyAndUploadContentUrisTask.OnCopyTmpFilesTaskListener {
 
-    private static final String TAG = Uploader.class.getSimpleName();
-
-    private static final String FTAG_TASK_RETAINER_FRAGMENT = "TASK_RETAINER_FRAGMENT";
+    private static final String TAG = ReceiveExternalFilesActivity.class.getSimpleName();
 
     private static final String FTAG_ERROR_FRAGMENT = "ERROR_FRAGMENT";
 
@@ -155,11 +148,13 @@ public class Uploader extends FileActivity
         // Init Fragment without UI to retain AsyncTask across configuration changes
         FragmentManager fm = getSupportFragmentManager();
         TaskRetainerFragment taskRetainerFragment =
-            (TaskRetainerFragment) fm.findFragmentByTag(FTAG_TASK_RETAINER_FRAGMENT);
+                (TaskRetainerFragment) fm.findFragmentByTag(TaskRetainerFragment.FTAG_TASK_RETAINER_FRAGMENT);
         if (taskRetainerFragment == null) {
             taskRetainerFragment = new TaskRetainerFragment();
-            fm.beginTransaction().add(taskRetainerFragment, FTAG_TASK_RETAINER_FRAGMENT).commit();
+            fm.beginTransaction()
+                    .add(taskRetainerFragment, TaskRetainerFragment.FTAG_TASK_RETAINER_FRAGMENT).commit();
         }   // else, Fragment already created and retained across configuration change
+
     }
 
     @Override
@@ -416,7 +411,7 @@ public class Uploader extends FileActivity
                                                 new String[] {"dirname"},
                                                 new int[] {R.id.filename},
                                                 getStorageManager(), getAccount());
-            
+
             mListView.setAdapter(sa);
             Button btnChooseFolder = (Button) findViewById(R.id.uploader_choose_folder);
             btnChooseFolder.setOnClickListener(this);
@@ -434,13 +429,13 @@ public class Uploader extends FileActivity
     }
 
     private void startSyncFolderOperation(OCFile folder) {
-        long currentSyncTime = System.currentTimeMillis(); 
-        
+        long currentSyncTime = System.currentTimeMillis();
+
         mSyncInProgress = true;
-        
+
         // perform folder synchronization
         RemoteOperation synchFolderOp = new RefreshFolderOperation( folder,
-                                                                        currentSyncTime, 
+                                                                        currentSyncTime,
                                                                         false,
                                                                         false,
                                                                         false,
@@ -476,124 +471,44 @@ public class Uploader extends FileActivity
     @SuppressLint("NewApi")
     public void uploadFiles() {
 
-        try {
-
-            List<Uri> contentUris = new ArrayList<>();
-            List<String> contentRemotePaths = new ArrayList<>();
+        UriUploader uploader = new UriUploader(
+                this,
+                mStreamsToUpload,
+                mUploadPath,
+                getAccount(),
+                FileUploader.LOCAL_BEHAVIOUR_FORGET,
+                true, // Show waiting dialog while file is being copied from private storage
+                this  // Copy temp task listener
+        );
 
-            int schemeFileCounter = 0;
+        UriUploader.UriUploaderResultCode resultCode = uploader.uploadUris();
 
-            for (Parcelable sourceStream : mStreamsToUpload) {
-                Uri sourceUri = (Uri) sourceStream;
-                if (sourceUri != null) {
-                    String displayName = UriUtils.getDisplayNameForUri(sourceUri, this);
-                    if (displayName == null) {
-                        displayName = generateDiplayName();
-                    }
-                    String remotePath = mUploadPath + displayName;
+        // Save the path to shared preferences; even if upload is not possible, user chose the folder
+        PreferenceManager.setLastUploadPath(mUploadPath, this);
 
-                    if (ContentResolver.SCHEME_CONTENT.equals(sourceUri.getScheme())) {
-                        contentUris.add(sourceUri);
-                        contentRemotePaths.add(remotePath);
-
-                    } else if (ContentResolver.SCHEME_FILE.equals(sourceUri.getScheme())) {
-                        /// file: uris should point to a local file, should be safe let FileUploader handle them
-                        requestUpload(sourceUri.getPath(), remotePath);
-                        schemeFileCounter++;
-                    }
-                }
-            }
-
-            if (!contentUris.isEmpty()) {
-                /// content: uris will be copied to temporary files before calling {@link FileUploader}
-                copyThenUpload(contentUris.toArray(new Uri[contentUris.size()]),
-                    contentRemotePaths.toArray(new String[contentRemotePaths.size()]));
+        if (resultCode == UriUploader.UriUploaderResultCode.OK) {
+            finish();
+        } else {
 
-            } else if (schemeFileCounter == 0) {
-                showErrorDialog(
-                    R.string.uploader_error_message_no_file_to_upload,
-                    R.string.uploader_error_title_no_file_to_upload
-                );
+            int messageResTitle = R.string.uploader_error_title_file_cannot_be_uploaded;
+            int messageResId = R.string.common_error_unknown;
 
-            } else {
-                finish();
+            if (resultCode == UriUploader.UriUploaderResultCode.ERROR_NO_FILE_TO_UPLOAD) {
+                messageResId = R.string.uploader_error_message_no_file_to_upload;
+                messageResTitle = R.string.uploader_error_title_no_file_to_upload;
+            } else if (resultCode == UriUploader.UriUploaderResultCode.ERROR_READ_PERMISSION_NOT_GRANTED) {
+                messageResId = R.string.uploader_error_message_read_permission_not_granted;
+            } else if (resultCode == UriUploader.UriUploaderResultCode.ERROR_UNKNOWN) {
+                messageResId = R.string.common_error_unknown;
             }
 
-        } catch (SecurityException e) {
-            Log_OC.e(TAG, "Permissions fail", e);
             showErrorDialog(
-                R.string.uploader_error_message_read_permission_not_granted,
-                R.string.uploader_error_title_file_cannot_be_uploaded
+                    messageResId,
+                    messageResTitle
             );
-
-        } catch (Exception e) {
-            Log_OC.e(TAG, "Unexpted error", e);
-            showErrorDialog(
-                R.string.common_error_unknown,
-                R.string.uploader_error_title_file_cannot_be_uploaded
-            );
-
-        } finally {
-            // Save the path to shared preferences; even if upload is not possible, user chose the folder
-            PreferenceManager.setLastUploadPath(mUploadPath, this);
         }
     }
 
-
-    private String generateDiplayName() {
-        return getString(R.string.common_unknown) +
-            "-" + DisplayUtils.unixTimeToHumanReadable(System.currentTimeMillis());
-    }
-
-    /**
-     *
-     * @param sourceUris        Array of content:// URIs to the files to upload
-     * @param remotePaths       Array of absolute paths to set to the uploaded files
-     */
-    private void copyThenUpload(Uri[] sourceUris, String[] remotePaths) {
-        showWaitingCopyDialog();
-
-        CopyAndUploadContentUrisTask copyTask = new CopyAndUploadContentUrisTask(this, this);
-        FragmentManager fm = getSupportFragmentManager();
-        TaskRetainerFragment taskRetainerFragment =
-            (TaskRetainerFragment) fm.findFragmentByTag(FTAG_TASK_RETAINER_FRAGMENT);
-        taskRetainerFragment.setTask(copyTask);
-        copyTask.execute(
-            CopyAndUploadContentUrisTask.makeParamsToExecute(
-                getAccount(),
-                sourceUris,
-                remotePaths,
-                getContentResolver()
-            )
-        );
-    }
-
-    /**
-     * Requests the upload of a file in the local file system to {@link FileUploader} service.
-     *
-     * The original file will be left in its original location, and will not be duplicated.
-     * As a side effect, the user will see the file as not uploaded when accesses to the OC app.
-     * This is considered as acceptable, since when a file is shared from another app to OC,
-     * the usual workflow will go back to the original app.
-     *
-     * @param localPath     Absolute path in the local file system to the file to upload.
-     * @param remotePath    Absolute path in the current OC account to set to the uploaded file.
-     */
-    private void requestUpload(String localPath, String remotePath) {
-        FileUploader.UploadRequester requester = new FileUploader.UploadRequester();
-        requester.uploadNewFile(
-            this,
-            getAccount(),
-            localPath,
-            remotePath,
-            FileUploader.LOCAL_BEHAVIOUR_FORGET,
-            null,       // MIME type will be detected from file name
-            false,      // do not create parent folder if not existent
-            UploadFileOperation.CREATED_BY_USER
-        );
-    }
-
-
     @Override
     public void onRemoteOperationFinish(RemoteOperation operation, RemoteOperationResult result) {
         super.onRemoteOperationFinish(operation, result);
@@ -687,7 +602,7 @@ public class Uploader extends FileActivity
         }
         return retval;
     }
-    
+
     private OCFile getCurrentFolder(){
         OCFile file = mFile;
         if (file != null) {
@@ -699,13 +614,13 @@ public class Uploader extends FileActivity
         }
         return null;
     }
-    
+
     private void browseToRoot() {
         OCFile root = getStorageManager().getFileByPath(OCFile.ROOT_PATH);
         mFile = root;
         startSyncFolderOperation(root);
     }
-    
+
     private class SyncBroadcastReceiver extends BroadcastReceiver {
 
         /**
@@ -737,7 +652,7 @@ public class Uploader extends FileActivity
                                 getStorageManager().getFileByPath(getCurrentFolder().getRemotePath());
 
                         if (currentDir == null) {
-                            // current folder was removed from the server 
+                            // current folder was removed from the server
                             Toast.makeText(context,
                                     String.format(
                                             getString(R.string.sync_current_folder_was_removed),
@@ -784,7 +699,7 @@ public class Uploader extends FileActivity
 
                 }
             } catch (RuntimeException e) {
-                // avoid app crashes after changing the serial id of RemoteOperationResult 
+                // avoid app crashes after changing the serial id of RemoteOperationResult
                 // in owncloud library with broadcast notifications pending to process
                 removeStickyBroadcast(intent);
             }
@@ -796,36 +711,10 @@ public class Uploader extends FileActivity
      */
     @Override
     public void onTmpFilesCopied(ResultCode result) {
-        dismissWaitingCopyDialog();
+        dismissLoadingDialog();
         finish();
     }
 
-    /**
-     * Show waiting for copy dialog
-     */
-    public void showWaitingCopyDialog() {
-        // Construct dialog
-        LoadingDialog loading = new LoadingDialog(
-                getResources().getString(R.string.wait_for_tmp_copy_from_private_storage));
-        FragmentManager fm = getSupportFragmentManager();
-        FragmentTransaction ft = fm.beginTransaction();
-        loading.show(ft, DIALOG_WAIT_COPY_FILE);
-
-    }
-
-
-    /**
-     * Dismiss waiting for copy dialog
-     */
-    public void dismissWaitingCopyDialog() {
-        Fragment frag = getSupportFragmentManager().findFragmentByTag(DIALOG_WAIT_COPY_FILE);
-        if (frag != null) {
-            LoadingDialog loading = (LoadingDialog) frag;
-            loading.dismiss();
-        }
-    }
-
-
     /**
      * Show an error dialog, forcing the user to click a single button to exit the activity
      *
@@ -860,52 +749,4 @@ public class Uploader extends FileActivity
         );
         errorDialog.show(getSupportFragmentManager(), FTAG_ERROR_FRAGMENT);
     }
-
-
-    /**
-     * Fragment retaining a background task across configuration changes.
-     */
-    public static class TaskRetainerFragment extends Fragment {
-
-        private CopyAndUploadContentUrisTask mTask;
-
-        /**
-         * Updates the listener of the retained task whenever the parent
-         * Activity is attached.
-         *
-         * Since its done in main thread, and provided the AsyncTask only accesses
-         * the listener in the main thread (should so), no sync problem should occur.
-         */
-        @Override
-        public void onAttach(Context context) {
-            super.onAttach(context);
-            if (mTask != null) {
-                mTask.setListener((CopyAndUploadContentUrisTask.OnCopyTmpFilesTaskListener) context);
-            }
-        }
-
-        /**
-         * Only called once, since the instance is retained across configuration changes
-         */
-        @Override
-        public void onCreate(Bundle savedInstanceState) {
-            super.onCreate(savedInstanceState);
-            setRetainInstance(true);    // the key point
-        }
-
-        /**
-         * Sets the task to retain across configuration changes
-         *
-         * @param task  Task to retain
-         */
-        private void setTask(CopyAndUploadContentUrisTask task) {
-            if (mTask != null) {
-                mTask.setListener(null);
-            }
-            mTask = task;
-            if (mTask != null && getContext() != null) {
-                task.setListener((CopyAndUploadContentUrisTask.OnCopyTmpFilesTaskListener) getContext());
-            }
-        }
-    }
 }

+ 2 - 2
src/com/owncloud/android/ui/asynctasks/CopyAndUploadContentUrisTask.java

@@ -66,7 +66,7 @@ public class CopyAndUploadContentUrisTask extends AsyncTask<Object, Void, Result
      * But we really, really, really want that the files are copied to temporary files in the OC folder and then
      * uploaded, even if the user gets bored of waiting while the copy finishes. And we can't forward the job to
      * another {@link Context}, because if any of the content:// URIs is constrained by a TEMPORARY READ PERMISSION,
-     * trying to open it will fail with a {@link SecurityException} after the user leaves the Uploader Activity. We
+     * trying to open it will fail with a {@link SecurityException} after the user leaves the ReceiveExternalFilesActivity Activity. We
      * really tried it.
      *
      * So we are doomed to leak here for the best interest of the user. Please, don't do similar in other places.
@@ -238,7 +238,7 @@ public class CopyAndUploadContentUrisTask extends AsyncTask<Object, Void, Result
             listener.onTmpFilesCopied(result);
 
         } else {
-            Log_OC.i(TAG, "User left Uploader activity before the temporal copies were finished ");
+            Log_OC.i(TAG, "User left the caller activity before the temporal copies were finished ");
             if (result != ResultCode.OK) {
                 // if the user left the app, report background error in a Toast
                 int messageId;

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

@@ -77,12 +77,12 @@ public class UploadSourceDialogFragment extends DialogFragment {
                             UploadFilesActivity.EXTRA_ACCOUNT,
                             ((FileActivity)getActivity()).getAccount()
                     );
-                    //startActivityForResult(action, REQUEST_CODE__SELECT_MULTIPLE_FILES);
+                    //startActivityForResult(action, REQUEST_CODE__SELECT_FILES_FROM_FILE_SYSTEM);
                     // this flow seems broken;
                     // Actionbarsherlock, maybe?
                     getActivity().startActivityForResult(
                             action,
-                            FileDisplayActivity.REQUEST_CODE__SELECT_MULTIPLE_FILES
+                            FileDisplayActivity.REQUEST_CODE__SELECT_FILES_FROM_FILE_SYSTEM
                     );
 
                 } else if (item == 1) {

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

@@ -229,7 +229,7 @@ public class OCFileListFragment extends ExtendedListFragment
             @Override
             public void onClick(View v) {
                 UploadFilesActivity.startUploadActivityForResult(getActivity(), ((FileActivity)getActivity())
-                        .getAccount(), FileDisplayActivity.REQUEST_CODE__SELECT_MULTIPLE_FILES);
+                        .getAccount(), FileDisplayActivity.REQUEST_CODE__SELECT_FILES_FROM_FILE_SYSTEM);
                 getFabMain().collapse();
                 recordMiniFabClick();
             }

+ 84 - 0
src/com/owncloud/android/ui/fragment/TaskRetainerFragment.java

@@ -0,0 +1,84 @@
+/**
+ *   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.fragment;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+
+import com.owncloud.android.ui.activity.ReceiveExternalFilesActivity;
+import com.owncloud.android.ui.asynctasks.CopyAndUploadContentUrisTask;
+
+/**
+ * Fragment retaining a background task across configuration changes.
+ */
+public class TaskRetainerFragment extends Fragment {
+
+    public static final String FTAG_TASK_RETAINER_FRAGMENT = "TASK_RETAINER_FRAGMENT";
+
+    private CopyAndUploadContentUrisTask mTask;
+
+    /**
+     * Updates the listener of the retained task whenever the parent
+     * Activity is attached.
+     *
+     * Since its done in main thread, and provided the AsyncTask only accesses
+     * the listener in the main thread (should so), no sync problem should occur.
+     */
+    @Override
+    public void onAttach(Context context) {
+        super.onAttach(context);
+        if (mTask != null) {
+            if (context instanceof ReceiveExternalFilesActivity) {
+                mTask.setListener((CopyAndUploadContentUrisTask.OnCopyTmpFilesTaskListener) context);
+            } else {
+                mTask.setListener(null);
+            }
+        }
+    }
+
+    /**
+     * Only called once, since the instance is retained across configuration changes
+     */
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setRetainInstance(true);    // the key point
+    }
+
+    /**
+     * Sets the task to retain across configuration changes
+     *
+     * @param task  Task to retain
+     */
+    public void setTask(CopyAndUploadContentUrisTask task) {
+        if (mTask != null) {
+            mTask.setListener(null);
+        }
+        mTask = task;
+        Context context = getContext();
+        if (mTask != null && context != null) {
+            if (context instanceof ReceiveExternalFilesActivity) {
+                task.setListener((CopyAndUploadContentUrisTask.OnCopyTmpFilesTaskListener) context);
+            } else {
+                task.setListener(null);
+            }
+        }
+    }
+}

+ 206 - 0
src/com/owncloud/android/ui/helpers/UriUploader.java

@@ -0,0 +1,206 @@
+/**
+ *   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.helpers;
+
+import android.accounts.Account;
+import android.content.ContentResolver;
+import android.net.Uri;
+import android.os.Parcelable;
+import android.support.v4.app.FragmentManager;
+
+import com.owncloud.android.R;
+import com.owncloud.android.files.services.FileUploader;
+import com.owncloud.android.lib.common.utils.Log_OC;
+import com.owncloud.android.operations.UploadFileOperation;
+import com.owncloud.android.ui.activity.FileActivity;
+import com.owncloud.android.ui.asynctasks.CopyAndUploadContentUrisTask;
+import com.owncloud.android.ui.fragment.TaskRetainerFragment;
+import com.owncloud.android.utils.DisplayUtils;
+import com.owncloud.android.utils.UriUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This class examines URIs pointing to files to upload and then requests {@link FileUploader} to upload them.
+ *
+ * URIs with scheme file:// do not require any previous processing, their path is sent to {@link FileUploader}
+ * to find the source file.
+ *
+ * URIs with scheme content:// are handling assuming that file is in private storage owned by a different app,
+ * and that persistency permission is not granted. Due to this, contents of the file are temporary copied by
+ * the OC app, and then passed {@link FileUploader}.
+ */
+public class UriUploader {
+
+    private final String TAG = UriUploader.class.getSimpleName();
+
+    private FileActivity mActivity;
+    private ArrayList<Parcelable> mUrisToUpload;
+    private CopyAndUploadContentUrisTask.OnCopyTmpFilesTaskListener mCopyTmpTaskListener;
+
+    private int mBehaviour;
+
+    private String mUploadPath;
+    private Account mAccount;
+    private boolean mShowWaitingDialog;
+
+    private UriUploaderResultCode mCode = UriUploaderResultCode.OK;
+
+    public enum UriUploaderResultCode {
+        OK,
+        ERROR_UNKNOWN,
+        ERROR_NO_FILE_TO_UPLOAD,
+        ERROR_READ_PERMISSION_NOT_GRANTED
+    }
+
+    public UriUploader(
+            FileActivity activity,
+            ArrayList<Parcelable> uris,
+            String uploadPath,
+            Account account,
+            int behaviour,
+            boolean showWaitingDialog,
+            CopyAndUploadContentUrisTask.OnCopyTmpFilesTaskListener copyTmpTaskListener
+    ) {
+        mActivity = activity;
+        mUrisToUpload = uris;
+        mUploadPath = uploadPath;
+        mAccount = account;
+        mBehaviour = behaviour;
+        mShowWaitingDialog = showWaitingDialog;
+        mCopyTmpTaskListener = copyTmpTaskListener;
+    }
+
+    public UriUploaderResultCode uploadUris() {
+
+        try {
+
+            List<Uri> contentUris = new ArrayList<>();
+            List<String> contentRemotePaths = new ArrayList<>();
+
+            int schemeFileCounter = 0;
+
+            for (Parcelable sourceStream : mUrisToUpload) {
+                Uri sourceUri = (Uri) sourceStream;
+                if (sourceUri != null) {
+                    String displayName = UriUtils.getDisplayNameForUri(sourceUri, mActivity);
+                    if (displayName == null) {
+                        displayName = generateDiplayName();
+                    }
+                    String remotePath = mUploadPath + displayName;
+
+                    if (ContentResolver.SCHEME_CONTENT.equals(sourceUri.getScheme())) {
+                        contentUris.add(sourceUri);
+                        contentRemotePaths.add(remotePath);
+
+                    } else if (ContentResolver.SCHEME_FILE.equals(sourceUri.getScheme())) {
+                        /// file: uris should point to a local file, should be safe let FileUploader handle them
+                        requestUpload(sourceUri.getPath(), remotePath);
+                        schemeFileCounter++;
+                    }
+                }
+            }
+
+            if (!contentUris.isEmpty()) {
+                /// content: uris will be copied to temporary files before calling {@link FileUploader}
+                copyThenUpload(contentUris.toArray(new Uri[contentUris.size()]),
+                        contentRemotePaths.toArray(new String[contentRemotePaths.size()]));
+
+            } else if (schemeFileCounter == 0) {
+                mCode = UriUploaderResultCode.ERROR_NO_FILE_TO_UPLOAD;
+
+            }
+
+        } catch (SecurityException e) {
+            mCode = UriUploaderResultCode.ERROR_READ_PERMISSION_NOT_GRANTED;
+            Log_OC.e(TAG, "Permissions fail", e);
+
+        } catch (Exception e) {
+            mCode = UriUploaderResultCode.ERROR_UNKNOWN;
+            Log_OC.e(TAG, "Unexpected error", e);
+
+        }
+        return mCode;
+    }
+
+    private String generateDiplayName() {
+        return mActivity.getString(R.string.common_unknown) +
+                "-" + DisplayUtils.unixTimeToHumanReadable(System.currentTimeMillis());
+    }
+
+    /**
+     * Requests the upload of a file in the local file system to {@link FileUploader} service.
+     *
+     * The original file will be left in its original location, and will not be duplicated.
+     * As a side effect, the user will see the file as not uploaded when accesses to the OC app.
+     * This is considered as acceptable, since when a file is shared from another app to OC,
+     * the usual workflow will go back to the original app.
+     *
+     * @param localPath     Absolute path in the local file system to the file to upload.
+     * @param remotePath    Absolute path in the current OC account to set to the uploaded file.
+     */
+    private void requestUpload(String localPath, String remotePath) {
+        FileUploader.UploadRequester requester = new FileUploader.UploadRequester();
+        requester.uploadNewFile(
+                mActivity,
+                mAccount,
+                localPath,
+                remotePath,
+                mBehaviour,
+                null,       // MIME type will be detected from file name
+                false,      // do not create parent folder if not existent
+                UploadFileOperation.CREATED_BY_USER
+        );
+    }
+
+    /**
+     *
+     * @param sourceUris        Array of content:// URIs to the files to upload
+     * @param remotePaths       Array of absolute paths to set to the uploaded files
+     */
+    private void copyThenUpload(Uri[] sourceUris, String[] remotePaths) {
+        if (mShowWaitingDialog) {
+            mActivity.showLoadingDialog(mActivity.getResources().
+                    getString(R.string.wait_for_tmp_copy_from_private_storage));
+        }
+
+        CopyAndUploadContentUrisTask copyTask = new CopyAndUploadContentUrisTask
+                (mCopyTmpTaskListener, mActivity);
+
+        FragmentManager fm = mActivity.getSupportFragmentManager();
+
+        // Init Fragment without UI to retain AsyncTask across configuration changes
+        TaskRetainerFragment taskRetainerFragment =
+                (TaskRetainerFragment) fm.findFragmentByTag(TaskRetainerFragment.FTAG_TASK_RETAINER_FRAGMENT);
+
+        if (taskRetainerFragment != null) {
+            taskRetainerFragment.setTask(copyTask);
+        }
+
+        copyTask.execute(
+                CopyAndUploadContentUrisTask.makeParamsToExecute(
+                        mAccount,
+                        sourceUris,
+                        remotePaths,
+                        mActivity.getContentResolver()
+                )
+        );
+    }
+}

+ 0 - 17
src/com/owncloud/android/utils/DisplayUtils.java

@@ -172,23 +172,6 @@ public class DisplayUtils {
         }
     }
 
-    /**
-     * Get the file extension if it is on path as type "content://.../DocInfo.doc"
-     * @param filepath: Content Uri converted to string format
-     * @return String: fileExtension (type '.pdf'). Empty if no extension
-     */
-    public static String getComposedFileExtension(String filepath) {
-        String fileExtension = "";
-        String fileNameInContentUri = filepath.substring(filepath.lastIndexOf("/"));
-
-        // Check if extension is included in uri
-        int pos = fileNameInContentUri.lastIndexOf('.');
-        if (pos >= 0) {
-            fileExtension = fileNameInContentUri.substring(pos);
-        }
-        return fileExtension;
-    }
-
     @SuppressWarnings("deprecation")
     public static CharSequence getRelativeDateTimeString (
             Context c, long time, long minResolution, long transitionResolution, int flags