ソースを参照

Merge pull request #1595 from owncloud/receive_files_from_apps__review

Receive files from apps  review
Juan Carlos González Cabrero 9 年 前
コミット
bf10885586

+ 3 - 1
AndroidManifest.xml

@@ -71,7 +71,9 @@
             </intent-filter>
         </activity>
         <activity android:name=".ui.activity.UploadFilesActivity" />
-        <activity android:name=".ui.activity.Uploader" >
+        <activity android:name=".ui.activity.Uploader"
+                  android:launchMode="singleInstance"
+                  android:excludeFromRecents="true">
             <intent-filter>
                 <action android:name="android.intent.action.SEND" />
 

+ 10 - 6
res/values/strings.xml

@@ -62,16 +62,18 @@
     <string name="sync_string_files">Files</string>
     <string name="setup_btn_connect">Connect</string>
     <string name="uploader_btn_upload_text">Upload</string>
-    <string name="uploader_btn_new_folder_text">New folder</string>
     <string name="uploader_top_message">Choose upload folder:</string>
     <string name="uploader_wrn_no_account_title">No account found</string>
     <string name="uploader_wrn_no_account_text">There are no %1$s accounts on your device. Please set up an account first.</string>
     <string name="uploader_wrn_no_account_setup_btn_text">Setup</string>
     <string name="uploader_wrn_no_account_quit_btn_text">Quit</string>
-    <string name="uploader_wrn_no_content_title">No content to upload</string>
-    <string name="uploader_wrn_no_content_text">No content was received. Nothing to upload.</string>
-    <string name="uploader_error_forbidden_content">%1$s is not allowed to access the shared content</string>
-    <string name="uploader_info_uploading">Uploading</string>
+    <string name="uploader_error_title_no_file_to_upload">No file to upload</string>
+    <string name="uploader_error_message_received_piece_of_text">%1$s cannot upload a piece of text as a file.</string>
+    <string name="uploader_error_message_no_file_to_upload">Received data do not include any valid file.</string>
+    <string name="uploader_error_title_file_cannot_be_uploaded">File cannot be uploaded</string>
+    <string name="uploader_error_message_read_permission_not_granted">%1$s is not allowed to read a received file</string>
+    <string name="uploader_error_message_source_file_not_found">File to upload was not found in its location. Please check whether the file exists.</string>
+    <string name="uploader_error_message_source_file_not_copied">An error occurred while copying the file to a temporary folder. Please try to send again.</string>
     <string name="file_list_seconds_ago">seconds ago</string>
     <string name="file_list_empty">Nothing in here. Upload something!</string>
     <string name="file_list_loading">Loading&#8230;</string>
@@ -99,9 +101,11 @@
     <string name="common_retry_upload">Retry upload</string>
     <string name="common_cancel_sync">Cancel sync</string>
     <string name="common_cancel">Cancel</string>
+    <string name="common_back">Back</string>
     <string name="common_save_exit">Save &amp; exit</string>
     <string name="common_error">Error</string>
     <string name="common_loading">Loading &#8230;</string>
+    <string name="common_unknown">unknown</string>
     <string name="common_error_unknown">Unknown error</string>
     <string name="about_title">About</string>
     <string name="change_password">Change password</string>
@@ -447,6 +451,6 @@
     <string name="manage_space_error">Some files could not be deleted.</string>
 
     <string name="permission_storage_access">Additional permissions required to upload &amp; download files.</string>
-    <string name="local_file_not_found_toast">The file was not found locally</string>
+    <string name="local_file_not_found_toast">The file was not found in the local file system</string>
 
 </resources>

+ 0 - 30
src/com/owncloud/android/db/OCUpload.java

@@ -394,34 +394,4 @@ public class OCUpload implements Parcelable {
 
     enum CanUploadFileNowStatus {NOW, LATER, FILE_GONE, ERROR};
 
-    /**
-     * Returns true when the file may be uploaded now. This methods checks all
-     * restraints of the passed {@link OCUpload}, these include
-     * isUseWifiOnly(), check if local file exists, check if file was already
-     * uploaded...
-     *
-     * If return value is CanUploadFileNowStatus.NOW, uploadFile() may be
-     * called.
-     *
-     * @return CanUploadFileNowStatus.NOW is upload may proceed, <br>
-     *         CanUploadFileNowStatus.LATER if upload should be performed at a
-     *         later time, <br>
-     *         CanUploadFileNowStatus.ERROR if a severe error happened, calling
-     *         entity should remove upload from queue.
-     *
-     */
-    private CanUploadFileNowStatus canUploadFileNow(Context context) {
-
-        if (getUploadStatus() == UploadStatus.UPLOAD_SUCCEEDED) {
-            Log_OC.w(TAG, "Already succeeded uploadObject was again scheduled for upload. Fix that!");
-            return CanUploadFileNowStatus.ERROR;
-        }
-
-        if (!new File(getLocalPath()).exists()) {
-            Log_OC.d(TAG, "Do not start upload because local file does not exist.");
-            return CanUploadFileNowStatus.FILE_GONE;
-        }
-        return CanUploadFileNowStatus.NOW;
-    }
-
 }

+ 91 - 0
src/com/owncloud/android/db/PreferenceManager.java

@@ -0,0 +1,91 @@
+/**
+ * ownCloud Android client application
+ *
+ * @author David A. Velasco
+ * Copyright (C) 2016 ownCloud Inc.
+ * <p/>
+ * 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.
+ * <p/>
+ * 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.
+ * <p/>
+ * 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.db;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+
+/**
+ * Helper to simplify reading of Preferences all around the app
+ */
+
+public class PreferenceManager {
+
+    /**
+     * Constant to access value of last path selected by the user to upload a file shared from other app.
+     * Value handled by the app without direct access in the UI.
+     */
+    private static final String AUTO_PREF__LAST_UPLOAD_PATH = "last_upload_path";
+
+    public static boolean instantPictureUploadEnabled(Context context) {
+        return android.preference.PreferenceManager.getDefaultSharedPreferences(context).getBoolean(
+                "instant_uploading",
+                false
+        );
+    }
+
+    public static boolean instantVideoUploadEnabled(Context context) {
+        return android.preference.PreferenceManager.getDefaultSharedPreferences(context).getBoolean(
+                "instant_video_uploading",
+                false
+        );
+    }
+
+    public static boolean instantPictureUploadViaWiFiOnly(Context context) {
+        return android.preference.PreferenceManager.getDefaultSharedPreferences(context).getBoolean(
+                "instant_upload_on_wifi",
+                false
+        );
+    }
+
+    public static boolean instantVideoUploadViaWiFiOnly(Context context) {
+        return android.preference.PreferenceManager.getDefaultSharedPreferences(context).getBoolean(
+                "instant_video_upload_on_wifi",
+                false
+        );
+    }
+
+    /**
+     * Gets the path where the user selected to do the last upload of a file shared from other app.
+     *
+     * @param context   Caller {@link Context}, used to access to shared preferences manager.
+     * @return path     Absolute path to a folder, as previously stored by {@link #setLastUploadPath(String, Context)},
+     *                  or empty String if never saved before.
+     */
+    public static String getLastUploadPath(Context context) {
+        SharedPreferences appPreferences = android.preference.PreferenceManager
+            .getDefaultSharedPreferences(context.getApplicationContext());
+        return appPreferences.getString(AUTO_PREF__LAST_UPLOAD_PATH, "");
+    }
+
+    /**
+     * Saves the path where the user selected to do the last upload of a file shared from other app.
+     *
+     * @param path      Absolute path to a folder.
+     * @param context   Caller {@link Context}, used to access to shared preferences manager.
+     */
+    public static void setLastUploadPath(String path, Context context) {
+        SharedPreferences.Editor appPrefs = android.preference.PreferenceManager
+            .getDefaultSharedPreferences(context.getApplicationContext()).edit();
+        appPrefs.putString(AUTO_PREF__LAST_UPLOAD_PATH, path);
+        appPrefs.apply();
+    }
+
+}

+ 0 - 59
src/com/owncloud/android/db/PreferenceReader.java

@@ -1,59 +0,0 @@
-/**
- * ownCloud Android client application
- *
- * @author David A. Velasco
- * Copyright (C) 2016 ownCloud Inc.
- * <p/>
- * 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.
- * <p/>
- * 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.
- * <p/>
- * 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.db;
-
-import android.content.Context;
-import android.preference.PreferenceManager;
-
-/**
- * Helper to simplify reading of Preferences all around the app
- */
-
-public class PreferenceReader {
-
-    public static boolean instantPictureUploadEnabled(Context context) {
-        return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(
-                "instant_uploading",
-                false
-        );
-    }
-
-    public static boolean instantVideoUploadEnabled(Context context) {
-        return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(
-                "instant_video_uploading",
-                false
-        );
-    }
-
-    public static boolean instantPictureUploadViaWiFiOnly(Context context) {
-        return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(
-                "instant_upload_on_wifi",
-                false
-        );
-    }
-
-    public static boolean instantVideoUploadViaWiFiOnly(Context context) {
-        return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(
-                "instant_video_upload_on_wifi",
-                false
-        );
-    }
-
-}

+ 4 - 5
src/com/owncloud/android/files/InstantUploadBroadcastReceiver.java

@@ -28,13 +28,12 @@ import android.content.Context;
 import android.content.Intent;
 import android.content.SharedPreferences;
 import android.database.Cursor;
-import android.preference.PreferenceManager;
 import android.provider.MediaStore.Images;
 import android.provider.MediaStore.Video;
 import android.support.v4.content.ContextCompat;
 
 import com.owncloud.android.authentication.AccountUtils;
-import com.owncloud.android.db.PreferenceReader;
+import com.owncloud.android.db.PreferenceManager;
 import com.owncloud.android.files.services.FileUploader;
 import com.owncloud.android.lib.common.utils.Log_OC;
 import com.owncloud.android.operations.UploadFileOperation;
@@ -87,7 +86,7 @@ public class InstantUploadBroadcastReceiver extends BroadcastReceiver {
 
         Log_OC.i(TAG, "New photo received");
 
-        if (!PreferenceReader.instantPictureUploadEnabled(context)) {
+        if (!PreferenceManager.instantPictureUploadEnabled(context)) {
             Log_OC.d(TAG, "Instant picture upload disabled, ignoring new picture");
             return;
         }
@@ -144,7 +143,7 @@ public class InstantUploadBroadcastReceiver extends BroadcastReceiver {
     }
 
     private Integer getUploadBehaviour(Context context) {
-        SharedPreferences appPreferences = PreferenceManager.getDefaultSharedPreferences(context);
+        SharedPreferences appPreferences = android.preference.PreferenceManager.getDefaultSharedPreferences(context);
         String behaviour = appPreferences.getString("prefs_instant_behaviour", "NOTHING");
 
         if (behaviour.equalsIgnoreCase("NOTHING")) {
@@ -165,7 +164,7 @@ public class InstantUploadBroadcastReceiver extends BroadcastReceiver {
 
         Log_OC.i(TAG, "New video received");
 
-        if (!PreferenceReader.instantVideoUploadEnabled(context)) {
+        if (!PreferenceManager.instantVideoUploadEnabled(context)) {
             Log_OC.d(TAG, "Instant video upload disabled, ignoring new video");
             return;
         }

+ 5 - 5
src/com/owncloud/android/files/services/ConnectivityActionReceiver.java

@@ -30,7 +30,7 @@ import android.net.wifi.WifiInfo;
 import android.net.wifi.WifiManager;
 import android.os.Bundle;
 
-import com.owncloud.android.db.PreferenceReader;
+import com.owncloud.android.db.PreferenceManager;
 import com.owncloud.android.db.UploadResult;
 import com.owncloud.android.lib.common.utils.Log_OC;
 
@@ -155,10 +155,10 @@ public class ConnectivityActionReceiver extends BroadcastReceiver {
     private void wifiConnected(Context context) {
         // for the moment, only recovery of instant uploads, similar to behaviour in release 1.9.1
         if (
-                (PreferenceReader.instantPictureUploadEnabled(context) &&
-                        PreferenceReader.instantPictureUploadViaWiFiOnly(context)) ||
-                (PreferenceReader.instantVideoUploadEnabled(context) &&
-                        PreferenceReader.instantVideoUploadViaWiFiOnly(context))
+                (PreferenceManager.instantPictureUploadEnabled(context) &&
+                        PreferenceManager.instantPictureUploadViaWiFiOnly(context)) ||
+                (PreferenceManager.instantVideoUploadEnabled(context) &&
+                        PreferenceManager.instantVideoUploadViaWiFiOnly(context))
                 ) {
             Log_OC.d(TAG, "Requesting retry of instant uploads (& friends)");
             FileUploader.UploadRequester requester = new FileUploader.UploadRequester();

+ 12 - 12
src/com/owncloud/android/files/services/FileUploader.java

@@ -481,9 +481,19 @@ public class FileUploader extends Service
             try {
                 for (int i = 0; i < files.length; i++) {
 
+                    OCUpload ocUpload = new OCUpload(files[i], account);
+                    ocUpload.setFileSize(files[i].getFileLength());
+                    ocUpload.setForceOverwrite(forceOverwrite);
+                    ocUpload.setCreateRemoteFolder(isCreateRemoteFolder);
+                    ocUpload.setCreatedBy(createdBy);
+                    ocUpload.setLocalAction(localAction);
+                    /*ocUpload.setUseWifiOnly(isUseWifiOnly);
+                    ocUpload.setWhileChargingOnly(isWhileChargingOnly);*/
+                    ocUpload.setUploadStatus(UploadStatus.UPLOAD_IN_PROGRESS);
+
                     newUpload = new UploadFileOperation(
                             account,
-                            files[i],
+                            ocUpload,
                             chunked,
                             forceOverwrite,
                             localAction,
@@ -498,17 +508,6 @@ public class FileUploader extends Service
 
                     newUpload.addRenameUploadListener(this);
 
-                    // Save upload in database
-                    OCUpload ocUpload = new OCUpload(files[i], account);
-                    ocUpload.setFileSize(files[i].getFileLength());
-                    ocUpload.setForceOverwrite(forceOverwrite);
-                    ocUpload.setCreateRemoteFolder(isCreateRemoteFolder);
-                    ocUpload.setCreatedBy(createdBy);
-                    ocUpload.setLocalAction(localAction);
-                    /*ocUpload.setUseWifiOnly(isUseWifiOnly);
-                    ocUpload.setWhileChargingOnly(isWhileChargingOnly);*/
-                    ocUpload.setUploadStatus(UploadStatus.UPLOAD_IN_PROGRESS);
-
                     Pair<String, String> putResult = mPendingUploads.putIfAbsent(
                             account.name,
                             files[i].getRemotePath(),
@@ -518,6 +517,7 @@ public class FileUploader extends Service
                         uploadKey = putResult.first;
                         requestedUploads.add(uploadKey);
 
+                        // Save upload in database
                         long id = mUploadsStorageManager.storeUpload(ocUpload);
                         newUpload.setOCUploadId(id);
                     }

+ 7 - 66
src/com/owncloud/android/operations/UploadFileOperation.java

@@ -27,7 +27,7 @@ import android.net.Uri;
 import com.owncloud.android.datamodel.FileDataStorageManager;
 import com.owncloud.android.datamodel.OCFile;
 import com.owncloud.android.db.OCUpload;
-import com.owncloud.android.db.PreferenceReader;
+import com.owncloud.android.db.PreferenceManager;
 import com.owncloud.android.files.services.FileUploader;
 import com.owncloud.android.lib.common.OwnCloudClient;
 import com.owncloud.android.lib.common.network.OnDatatransferProgressListener;
@@ -70,31 +70,10 @@ import java.util.concurrent.atomic.AtomicBoolean;
  */
 public class UploadFileOperation extends SyncOperation {
 
-
-    private static final String MIME_TYPE_PDF = "application/pdf";
-    private static final String FILE_EXTENSION_PDF = ".pdf";
-
     public static final int CREATED_BY_USER = 0;
     public static final int CREATED_AS_INSTANT_PICTURE = 1;
     public static final int CREATED_AS_INSTANT_VIDEO = 2;
 
-
-    /**
-     * Checks if content provider, using the content:// scheme, returns a file with mime-type
-     * 'application/pdf' but file has not extension
-     * @param localPath         Full path to a file in the local file system.
-     * @param mimeType          MIME type of the file.
-     * @return true if is needed to add the pdf file extension to the file
-     *
-     * TODO - move to OCFile or Utils class
-     */
-    private static boolean isPdfFileFromContentProviderWithoutExtension(String localPath,
-                                                                        String mimeType) {
-        return localPath.startsWith(UriUtils.URI_CONTENT_SCHEME) &&
-                mimeType.equals(MIME_TYPE_PDF) &&
-                !localPath.endsWith(FILE_EXTENSION_PDF);
-    }
-
     public static OCFile obtainNewOCFileToUpload(String remotePath, String localPath, String mimeType) {
 
         // MIME type
@@ -102,11 +81,6 @@ public class UploadFileOperation extends SyncOperation {
             mimeType = MimetypeIconUtil.getBestMimeTypeByFilename(localPath);
         }
 
-        // TODO - this is a horrible special case that should not be handled this way
-        if (isPdfFileFromContentProviderWithoutExtension(localPath, mimeType)){
-            remotePath += FILE_EXTENSION_PDF;
-        }
-
         OCFile newFile = new OCFile(remotePath);
         newFile.setStoragePath(localPath);
         newFile.setLastSyncDateForProperties(0);
@@ -126,8 +100,6 @@ public class UploadFileOperation extends SyncOperation {
         return newFile;
     }
 
-
-
     private static final String TAG = UploadFileOperation.class.getSimpleName();
 
     private Account mAccount;
@@ -148,7 +120,6 @@ public class UploadFileOperation extends SyncOperation {
     private int mCreatedBy = CREATED_BY_USER;
 
     private boolean mWasRenamed = false;
-    private String mOriginalFileName = null;
     private long mOCUploadId = -1;
     /**
      * Local path to file which is to be uploaded (before any possible renaming or moving).
@@ -166,35 +137,6 @@ public class UploadFileOperation extends SyncOperation {
 
     protected RequestEntity mEntity = null;
 
-    public UploadFileOperation(Account account,
-                               OCFile file,
-                               boolean chunked,
-                               boolean forceOverwrite,
-                               int localBehaviour,
-                               Context context
-    ) {
-        if (account == null)
-            throw new IllegalArgumentException("Illegal NULL account in UploadFileOperation " +
-                    "creation");
-        if (file == null)
-            throw new IllegalArgumentException("Illegal NULL file in UploadFileOperation creation");
-        if (file.getStoragePath() == null || file.getStoragePath().length() <= 0) {
-            throw new IllegalArgumentException(
-                    "Illegal file in UploadFileOperation; storage path invalid: "
-                            + file.getStoragePath());
-        }
-
-        mAccount = account;
-        mFile = file;
-        mRemotePath = file.getRemotePath();
-        mChunked = chunked;
-        mForceOverwrite = forceOverwrite;
-        mLocalBehaviour = localBehaviour;
-        mOriginalStoragePath = mFile.getStoragePath();
-        mOriginalFileName = mFile.getFileName();
-        mContext = context;
-    }
-
     public UploadFileOperation(Account account,
                                OCUpload upload,
                                boolean chunked,
@@ -215,16 +157,15 @@ public class UploadFileOperation extends SyncOperation {
 
         mAccount = account;
         mFile = obtainNewOCFileToUpload(
-                upload.getRemotePath(),
-                upload.getLocalPath(),
-                upload.getMimeType()
+            upload.getRemotePath(),
+            upload.getLocalPath(),
+            upload.getMimeType()
         );
         mRemotePath = upload.getRemotePath();
         mChunked = chunked;
         mForceOverwrite = forceOverwrite;
         mLocalBehaviour = localBehaviour;
         mOriginalStoragePath = mFile.getStoragePath();
-        mOriginalFileName = mFile.getFileName();
         mContext = context;
         mOCUploadId = upload.getUploadId();
         mCreatedBy = upload.getCreadtedBy();
@@ -236,7 +177,7 @@ public class UploadFileOperation extends SyncOperation {
     }
 
     public String getFileName() {
-        return mOriginalFileName;
+        return (mFile != null) ? mFile.getFileName() : null;
     }
 
     public OCFile getFile() {
@@ -503,10 +444,10 @@ public class UploadFileOperation extends SyncOperation {
      */
     private boolean delayForWifi() {
         boolean delayInstantPicture = (
-            isInstantPicture() &&  PreferenceReader.instantPictureUploadViaWiFiOnly(mContext)
+            isInstantPicture() &&  PreferenceManager.instantPictureUploadViaWiFiOnly(mContext)
         );
         boolean delayInstantVideo = (
-            isInstantVideo() && PreferenceReader.instantVideoUploadViaWiFiOnly(mContext)
+            isInstantVideo() && PreferenceManager.instantVideoUploadViaWiFiOnly(mContext)
         );
         return (
             (delayInstantPicture || delayInstantVideo) &&

+ 1 - 1
src/com/owncloud/android/ui/activity/FileActivity.java

@@ -1074,7 +1074,7 @@ public class FileActivity extends AppCompatActivity
     @Override
     public void onFailedSavingCertificate() {
         ConfirmationDialogFragment dialog = ConfirmationDialogFragment.newInstance(
-            R.string.ssl_validator_not_saved, new String[]{}, R.string.common_ok, -1, -1
+            R.string.ssl_validator_not_saved, new String[]{}, 0, R.string.common_ok, -1, -1
         );
         dialog.show(getSupportFragmentManager(), DIALOG_CERT_NOT_SAVED);
     }

+ 1 - 1
src/com/owncloud/android/ui/activity/UploadFilesActivity.java

@@ -398,7 +398,7 @@ public class UploadFilesActivity extends FileActivity implements
                 // to the ownCloud folder instead of copying
                 String[] args = {getString(R.string.app_name)};
                 ConfirmationDialogFragment dialog = ConfirmationDialogFragment.newInstance(
-                    R.string.upload_query_move_foreign_files, args, R.string.common_yes, -1,
+                    R.string.upload_query_move_foreign_files, args, 0, R.string.common_yes, -1,
                         R.string.common_no
                 );
                 dialog.setOnConfirmationListener(UploadFilesActivity.this);

+ 236 - 206
src/com/owncloud/android/ui/activity/Uploader.java

@@ -3,6 +3,8 @@
  *
  *  @author Bartek Przybylski
  *  @author masensio
+ *  @author Juan Carlos González Cabrero
+ *  @author David A. Velasco
  *  Copyright (C) 2012  Bartek Przybylski
  *  Copyright (C) 2016 ownCloud Inc.
  *
@@ -26,25 +28,18 @@ import android.accounts.AccountManager;
 import android.accounts.AuthenticatorException;
 import android.annotation.SuppressLint;
 import android.app.Dialog;
-import android.app.ProgressDialog;
 import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
 import android.content.Context;
 import android.content.DialogInterface;
 import android.content.IntentFilter;
 import android.content.DialogInterface.OnCancelListener;
 import android.content.DialogInterface.OnClickListener;
 import android.content.Intent;
-import android.content.SharedPreferences;
 import android.content.res.Resources.NotFoundException;
-import android.database.Cursor;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.Parcelable;
-import android.preference.PreferenceManager;
-import android.provider.MediaStore;
-import android.provider.MediaStore.Audio;
-import android.provider.MediaStore.Images;
-import android.provider.MediaStore.Video;
 import android.support.v4.app.Fragment;
 import android.support.v4.app.FragmentManager;
 import android.support.v4.app.FragmentTransaction;
@@ -58,15 +53,14 @@ import android.view.View;
 import android.widget.AdapterView;
 import android.widget.AdapterView.OnItemClickListener;
 import android.widget.Button;
-import android.widget.EditText;
 import android.widget.ListView;
-import android.widget.ProgressBar;
 import android.widget.Toast;
 
 import com.owncloud.android.MainApp;
 import com.owncloud.android.R;
 import com.owncloud.android.authentication.AccountAuthenticator;
 import com.owncloud.android.datamodel.OCFile;
+import com.owncloud.android.db.PreferenceManager;
 import com.owncloud.android.files.services.FileUploader;
 import com.owncloud.android.lib.common.operations.RemoteOperation;
 import com.owncloud.android.lib.common.operations.RemoteOperationResult;
@@ -77,13 +71,14 @@ 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.utils.CopyTmpFileAsyncTask;
+import com.owncloud.android.ui.asynctasks.CopyAndUploadContentUrisTask;
 import com.owncloud.android.utils.DisplayUtils;
 import com.owncloud.android.utils.ErrorMessageAdapter;
+import com.owncloud.android.utils.UriUtils;
 
-import java.io.File;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.LinkedList;
@@ -97,14 +92,17 @@ import java.util.Vector;
  */
 public class Uploader extends FileActivity
         implements OnItemClickListener, android.view.View.OnClickListener,
-        CopyTmpFileAsyncTask.OnCopyTmpFileTaskListener {
+    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 FTAG_ERROR_FRAGMENT = "ERROR_FRAGMENT";
+
     private AccountManager mAccountManager;
     private Stack<String> mParents;
     private ArrayList<Parcelable> mStreamsToUpload;
-    private boolean mCreateDir;
     private String mUploadPath;
     private OCFile mFile;
 
@@ -113,13 +111,8 @@ public class Uploader extends FileActivity
     private boolean mAccountSelected;
     private boolean mAccountSelectionShowing;
 
-    private ArrayList<String> mRemoteCacheData;
-    private int mNumCacheFile;
-
     private final static int DIALOG_NO_ACCOUNT = 0;
-    private final static int DIALOG_WAITING = 1;
-    private final static int DIALOG_NO_STREAM = 2;
-    private final static int DIALOG_MULTIPLE_ACCOUNT = 3;
+    private final static int DIALOG_MULTIPLE_ACCOUNT = 1;
 
     private final static int REQUEST_CODE__SETUP_ACCOUNT = REQUEST_CODE__LAST_SHARED + 1;
 
@@ -127,8 +120,6 @@ public class Uploader extends FileActivity
     private final static String KEY_FILE = "FILE";
     private final static String KEY_ACCOUNT_SELECTED = "ACCOUNT_SELECTED";
     private final static String KEY_ACCOUNT_SELECTION_SHOWING = "ACCOUNT_SELECTION_SHOWING";
-    private final static String KEY_NUM_CACHE_FILE = "NUM_CACHE_FILE";
-    private final static String KEY_REMOTE_CACHE_DATA = "REMOTE_CACHE_DATA";
 
     private static final String DIALOG_WAIT_COPY_FILE = "DIALOG_WAIT_COPY_FILE";
 
@@ -137,20 +128,15 @@ public class Uploader extends FileActivity
         prepareStreamsToUpload();
 
         if (savedInstanceState == null) {
-            mParents = new Stack<String>();
+            mParents = new Stack<>();
             mAccountSelected = false;
             mAccountSelectionShowing = false;
-            mNumCacheFile = 0;
 
-            // ArrayList for files with path in private storage
-            mRemoteCacheData = new ArrayList<String>();
         } else {
             mParents = (Stack<String>) savedInstanceState.getSerializable(KEY_PARENTS);
             mFile = savedInstanceState.getParcelable(KEY_FILE);
             mAccountSelected = savedInstanceState.getBoolean(KEY_ACCOUNT_SELECTED);
             mAccountSelectionShowing = savedInstanceState.getBoolean(KEY_ACCOUNT_SELECTION_SHOWING);
-            mNumCacheFile = savedInstanceState.getInt(KEY_NUM_CACHE_FILE);
-            mRemoteCacheData = savedInstanceState.getStringArrayList(KEY_REMOTE_CACHE_DATA);
         }
 
         super.onCreate(savedInstanceState);
@@ -165,6 +151,15 @@ public class Uploader extends FileActivity
         syncIntentFilter.addAction(RefreshFolderOperation.EVENT_SINGLE_FOLDER_SHARES_SYNCED);
         mSyncBroadcastReceiver = new SyncBroadcastReceiver();
         registerReceiver(mSyncBroadcastReceiver, syncIntentFilter);
+
+        // Init Fragment without UI to retain AsyncTask across configuration changes
+        FragmentManager fm = getSupportFragmentManager();
+        TaskRetainerFragment taskRetainerFragment =
+            (TaskRetainerFragment) fm.findFragmentByTag(FTAG_TASK_RETAINER_FRAGMENT);
+        if (taskRetainerFragment == null) {
+            taskRetainerFragment = new TaskRetainerFragment();
+            fm.beginTransaction().add(taskRetainerFragment, FTAG_TASK_RETAINER_FRAGMENT).commit();
+        }   // else, Fragment already created and retained across configuration change
     }
 
     @Override
@@ -185,8 +180,17 @@ public class Uploader extends FileActivity
                 }
             }
 
+        } else if (getIntent().getStringExtra(Intent.EXTRA_TEXT) != null) {
+            showErrorDialog(
+                R.string.uploader_error_message_received_piece_of_text,
+                R.string.uploader_error_title_no_file_to_upload
+            );
+
         } else {
-            showDialog(DIALOG_NO_STREAM);
+            showErrorDialog(
+                R.string.uploader_error_message_no_file_to_upload,
+                R.string.uploader_error_title_no_file_to_upload
+            );
         }
 
         super.setAccount(account, savedAccount);
@@ -208,8 +212,6 @@ public class Uploader extends FileActivity
         outState.putParcelable(KEY_FILE, mFile);
         outState.putBoolean(KEY_ACCOUNT_SELECTED, mAccountSelected);
         outState.putBoolean(KEY_ACCOUNT_SELECTION_SHOWING, mAccountSelectionShowing);
-        outState.putInt(KEY_NUM_CACHE_FILE, mNumCacheFile);
-        outState.putStringArrayList(KEY_REMOTE_CACHE_DATA, mRemoteCacheData);
         outState.putParcelable(FileActivity.EXTRA_ACCOUNT, getAccount());
 
         Log_OC.d(TAG, "onSaveInstanceState() end");
@@ -227,21 +229,6 @@ public class Uploader extends FileActivity
     protected Dialog onCreateDialog(final int id) {
         final AlertDialog.Builder builder = new Builder(this);
         switch (id) {
-        case DIALOG_WAITING:
-            final ProgressDialog pDialog = new ProgressDialog(this, R.style.ProgressDialogTheme);
-            pDialog.setIndeterminate(false);
-            pDialog.setCancelable(false);
-            pDialog.setMessage(getResources().getString(R.string.uploader_info_uploading));
-            pDialog.setOnShowListener(new DialogInterface.OnShowListener() {
-                @Override
-                public void onShow(DialogInterface dialog) {
-                    ProgressBar v = (ProgressBar) pDialog.findViewById(android.R.id.progress);
-                    v.getIndeterminateDrawable().setColorFilter(getResources().getColor(R.color.color_accent),
-                            android.graphics.PorterDuff.Mode.MULTIPLY);
-
-                }
-            });
-            return pDialog;
         case DIALOG_NO_ACCOUNT:
             builder.setIcon(R.drawable.ic_warning);
             builder.setTitle(R.string.uploader_wrn_no_account_title);
@@ -308,45 +295,15 @@ public class Uploader extends FileActivity
                 }
             });
             return builder.create();
-        case DIALOG_NO_STREAM:
-            builder.setIcon(R.drawable.ic_warning);
-            builder.setTitle(R.string.uploader_wrn_no_content_title);
-            builder.setMessage(R.string.uploader_wrn_no_content_text);
-            builder.setCancelable(false);
-            builder.setNegativeButton(R.string.common_cancel, new OnClickListener() {
-                @Override
-                public void onClick(DialogInterface dialog, int which) {
-                    finish();
-                }
-            });
-            return builder.create();
         default:
             throw new IllegalArgumentException("Unknown dialog id: " + id);
         }
     }
 
-    class a implements OnClickListener {
-        String mPath;
-        EditText mDirname;
-
-        public a(String path, EditText dirname) {
-            mPath = path;
-            mDirname = dirname;
-        }
-
-        @Override
-        public void onClick(DialogInterface dialog, int which) {
-            Uploader.this.mUploadPath = mPath + mDirname.getText().toString();
-            Uploader.this.mCreateDir = true;
-            uploadFiles();
-        }
-    }
-
     @Override
     public void onBackPressed() {
         if (mParents.size() <= 1) {
             super.onBackPressed();
-            return;
         } else {
             mParents.pop();
             String full_path = generatePath(mParents);
@@ -363,7 +320,7 @@ public class Uploader extends FileActivity
         Vector<OCFile> tmpfiles = getStorageManager().getFolderContent(mFile /*, false*/);
         if (tmpfiles.size() <= 0) return;
         // filter on dirtype
-        Vector<OCFile> files = new Vector<OCFile>();
+        Vector<OCFile> files = new Vector<>();
         for (OCFile f : tmpfiles)
                 files.add(f);
         if (files.size() < position) {
@@ -384,19 +341,17 @@ public class Uploader extends FileActivity
             case R.id.uploader_choose_folder:
                 mUploadPath = "";   // first element in mParents is root dir, represented by "";
                 // init mUploadPath with "/" results in a "//" prefix
-                for (String p : mParents)
+                for (String p : mParents) {
                     mUploadPath += p + OCFile.PATH_SEPARATOR;
+                }
                 Log_OC.d(TAG, "Uploading file to dir " + mUploadPath);
-
                 uploadFiles();
-
                 break;
 
             case R.id.uploader_cancel:
                 finish();
                 break;
 
-
             default:
                 throw new IllegalArgumentException("Wrong element clicked");
         }
@@ -448,9 +403,9 @@ public class Uploader extends FileActivity
         if (mFile != null) {
             // TODO Enable when "On Device" is recovered ?
             Vector<OCFile> files = getStorageManager().getFolderContent(mFile/*, false*/);
-            List<HashMap<String, OCFile>> data = new LinkedList<HashMap<String,OCFile>>();
+            List<HashMap<String, OCFile>> data = new LinkedList<>();
             for (OCFile f : files) {
-                HashMap<String, OCFile> h = new HashMap<String, OCFile>();
+                HashMap<String, OCFile> h = new HashMap<>();
                     h.put("dirname", f);
                     data.add(h);
             }
@@ -507,7 +462,7 @@ public class Uploader extends FileActivity
 
     private void prepareStreamsToUpload() {
         if (getIntent().getAction().equals(Intent.ACTION_SEND)) {
-            mStreamsToUpload = new ArrayList<Parcelable>();
+            mStreamsToUpload = new ArrayList<>();
             mStreamsToUpload.add(getIntent().getParcelableExtra(Intent.EXTRA_STREAM));
         } else if (getIntent().getAction().equals(Intent.ACTION_SEND_MULTIPLE)) {
             mStreamsToUpload = getIntent().getParcelableArrayListExtra(Intent.EXTRA_STREAM);
@@ -520,110 +475,125 @@ public class Uploader extends FileActivity
 
     @SuppressLint("NewApi")
     public void uploadFiles() {
+
         try {
-            // this checks the mimeType
-            for (Parcelable mStream : mStreamsToUpload) {
-
-                Uri uri = (Uri) mStream;
-                String data = null;
-                String filePath = "";
-
-                if (uri != null) {
-                    if (uri.getScheme().equals("content")) {
-                        String mimeType = getContentResolver().getType(uri);
-
-                        if (mimeType.contains("image")) {
-                            String[] CONTENT_PROJECTION = {Images.Media.DATA,
-                                    Images.Media.DISPLAY_NAME, Images.Media.MIME_TYPE,
-                                    Images.Media.SIZE};
-                            Cursor c = getContentResolver().query(uri, CONTENT_PROJECTION, null,
-                                    null, null);
-                            c.moveToFirst();
-                            int index = c.getColumnIndex(Images.Media.DATA);
-                            data = c.getString(index);
-                            filePath = mUploadPath +
-                                    c.getString(c.getColumnIndex(Images.Media.DISPLAY_NAME));
-
-                        } else if (mimeType.contains("video")) {
-                            String[] CONTENT_PROJECTION = {Video.Media.DATA,
-                                    Video.Media.DISPLAY_NAME, Video.Media.MIME_TYPE,
-                                    Video.Media.SIZE, Video.Media.DATE_MODIFIED};
-                            Cursor c = getContentResolver().query(uri, CONTENT_PROJECTION, null,
-                                    null, null);
-                            c.moveToFirst();
-                            int index = c.getColumnIndex(Video.Media.DATA);
-                            data = c.getString(index);
-                            filePath = mUploadPath +
-                                    c.getString(c.getColumnIndex(Video.Media.DISPLAY_NAME));
-
-                        } else if (mimeType.contains("audio")) {
-                            String[] CONTENT_PROJECTION = {Audio.Media.DATA,
-                                    Audio.Media.DISPLAY_NAME, Audio.Media.MIME_TYPE,
-                                    Audio.Media.SIZE};
-                            Cursor c = getContentResolver().query(uri, CONTENT_PROJECTION, null,
-                                    null, null);
-                            c.moveToFirst();
-                            int index = c.getColumnIndex(Audio.Media.DATA);
-                            data = c.getString(index);
-                            filePath = mUploadPath +
-                                    c.getString(c.getColumnIndex(Audio.Media.DISPLAY_NAME));
 
-                        } else {
-                            Cursor cursor = getContentResolver().query(uri,
-                                    new String[]{MediaStore.MediaColumns.DISPLAY_NAME},
-                                    null, null, null);
-                            cursor.moveToFirst();
-                            int nameIndex = cursor.getColumnIndex(cursor.getColumnNames()[0]);
-                            if (nameIndex >= 0) {
-                                filePath = mUploadPath + cursor.getString(nameIndex);
-                            }
-                        }
+            List<Uri> contentUris = new ArrayList<>();
+            List<String> contentRemotePaths = new ArrayList<>();
 
-                    } else if (uri.getScheme().equals("file")) {
-                        filePath = Uri.decode(uri.toString()).replace(uri.getScheme() +
-                                "://", "");
-                        if (filePath.contains("mnt")) {
-                            String splitedFilePath[] = filePath.split("/mnt");
-                            filePath = splitedFilePath[1];
-                        }
-                        final File file = new File(filePath);
-                        data = file.getAbsolutePath();
-                        filePath = mUploadPath + file.getName();
-                    } else {
-                        throw new SecurityException();
+            int schemeFileCounter = 0;
+
+            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;
+
+                    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 (data == null) {
-                        mRemoteCacheData.add(filePath);
-                        CopyTmpFileAsyncTask copyTask = new CopyTmpFileAsyncTask(this);
-                        Object[] params = {uri, filePath, mRemoteCacheData.size() - 1,
-                                getAccount().name, getContentResolver()};
-                        mNumCacheFile++;
-                        showWaitingCopyDialog();
-                        copyTask.execute(params);
-
-                    //} else {
-                    // TODO request to FileUploader with data as source file, resulting in lazy temporary copy
-                    //}
-                } else {
-                    throw new SecurityException();
                 }
+            }
 
-                //Save the path to shared preferences
-                SharedPreferences.Editor appPrefs = PreferenceManager
-                        .getDefaultSharedPreferences(getApplicationContext()).edit();
-                appPrefs.putString("last_upload_path", mUploadPath);
-                appPrefs.apply();
+            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) {
+                showErrorDialog(
+                    R.string.uploader_error_message_no_file_to_upload,
+                    R.string.uploader_error_title_no_file_to_upload
+                );
+
+            } else {
                 finish();
             }
 
         } catch (SecurityException e) {
-            String message = String.format(getString(R.string.uploader_error_forbidden_content),
-                    getString(R.string.app_name));
-            Toast.makeText(this, message, Toast.LENGTH_LONG).show();
+            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
+            );
+
+        } 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);
@@ -671,15 +641,12 @@ public class Uploader extends FileActivity
                     "initializing mStorageManager");
         }
 
-        SharedPreferences appPreferences = PreferenceManager
-                .getDefaultSharedPreferences(getApplicationContext());
-
-        String last_path = appPreferences.getString("last_upload_path", "");
+        String lastPath = PreferenceManager.getLastUploadPath(this);
         // "/" equals root-directory
-        if (last_path.equals("/")) {
+        if (lastPath.equals("/")) {
             mParents.add("");
         } else {
-            String[] dir_names = last_path.split("/");
+            String[] dir_names = lastPath.split("/");
             mParents.clear();
             for (String dir : dir_names)
                 mParents.add(dir);
@@ -823,35 +790,14 @@ public class Uploader extends FileActivity
             }
         }
     }
+
     /**
-     * Process the result of CopyTmpFileAsyncTask
-     *
-     * @param result
-     * @param index
+     * Process the result of CopyAndUploadContentUrisTask
      */
     @Override
-    public void onTmpFileCopied(String result, int index) {
-        if (mNumCacheFile-- == 0) {
-            dismissWaitingCopyDialog();
-        }
-        if (result != null) {
-            FileUploader.UploadRequester requester = new FileUploader.UploadRequester();
-            requester.uploadNewFile(
-                    this, getAccount(),
-                    result,
-                    mRemoteCacheData.get(index),
-                    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
-            );
-
-        } else {
-            String message = String.format(getString(R.string.uploader_error_forbidden_content),
-                    getString(R.string.app_name));
-            Toast.makeText(this, message, Toast.LENGTH_LONG).show();
-            Log_OC.d(TAG, message);
-        }
+    public void onTmpFilesCopied(ResultCode result) {
+        dismissWaitingCopyDialog();
+        finish();
     }
 
     /**
@@ -878,4 +824,88 @@ public class Uploader extends FileActivity
             loading.dismiss();
         }
     }
+
+
+    /**
+     * Show an error dialog, forcing the user to click a single button to exit the activity
+     *
+     * @param messageResId      Resource id of the message to show in the dialog.
+     * @param messageResTitle   Resource id of the title to show in the dialog. 0 to show default alert message.
+     *                          -1 to show no title.
+     */
+    private void showErrorDialog(int messageResId, int messageResTitle) {
+
+        ConfirmationDialogFragment errorDialog = ConfirmationDialogFragment.newInstance(
+            messageResId,
+            new String[]{getString(R.string.app_name)}, // see uploader_error_message_* in strings.xml
+            messageResTitle,
+            R.string.common_back,
+            -1,
+            -1
+        );
+        errorDialog.setCancelable(false);
+        errorDialog.setOnConfirmationListener(
+            new ConfirmationDialogFragment.ConfirmationDialogFragmentListener() {
+                @Override
+                public void onConfirmation(String callerTag) {
+                    finish();
+                }
+
+                @Override
+                public void onNeutral(String callerTag) {}
+
+                @Override
+                public void onCancel(String callerTag) {}
+            }
+        );
+        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());
+            }
+        }
+    }
 }

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

@@ -0,0 +1,282 @@
+/**
+ *   ownCloud Android client application
+ *
+ *   @author masensio
+ *   @author Juan Carlos González Cabrero
+ *   @author David A. Velasco
+ *   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.asynctasks;
+
+import android.accounts.Account;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.widget.Toast;
+
+import com.owncloud.android.R;
+import com.owncloud.android.files.services.FileUploader;
+import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode;
+import com.owncloud.android.lib.common.utils.Log_OC;
+import com.owncloud.android.operations.UploadFileOperation;
+import com.owncloud.android.utils.FileStorageUtils;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.lang.ref.WeakReference;
+
+/**
+ * AsyncTask to copy a file from a uri in a temporal file
+ */
+public class CopyAndUploadContentUrisTask extends AsyncTask<Object, Void, ResultCode> {
+
+    private final String TAG = CopyAndUploadContentUrisTask.class.getSimpleName();
+
+    /**
+     * Helper method building a correct array of parameters to be passed to {@link #execute(Object[])} )}
+     *
+     * Just packages the received parameters in correct order, doesn't check anything about them.
+     *
+     * @param   account             OC account to upload the shared files.
+     * @param   sourceUris          Array of "content://" URIs to the files to be uploaded.
+     * @param   remotePaths         Array of absolute paths in the OC account to set to the uploaded files.
+     * @param   contentResolver     {@link ContentResolver} instance with appropriate permissions to open the
+     *                              URIs in 'sourceUris'.
+     *
+     * Handling this parameter in {@link #doInBackground(Object[])} keeps an indirect reference to the
+     * caller Activity, what is technically wrong, since it will be held in memory
+     * (with all its associated resources) until the task finishes even though the user leaves the Activity.
+     *
+     * 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
+     * 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.
+     *
+     * Any idea to prevent this while keeping the functionality will be welcome.
+     *
+     * @return  Correct array of parameters to be passed to {@link #execute(Object[])}
+     */
+    public static Object[] makeParamsToExecute(
+        Account account,
+        Uri[] sourceUris,
+        String[] remotePaths,
+        ContentResolver contentResolver
+    ) {
+
+        return new Object[] {
+            account,
+            sourceUris,
+            remotePaths,
+            contentResolver
+        };
+    }
+
+    /**
+     * Listener in main thread to be notified when the task ends. Held in a WeakReference assuming that its
+     * lifespan is associated with an Activity context, that could be finished by the user before the AsyncTask
+     * ends.
+     */
+    private WeakReference<OnCopyTmpFilesTaskListener> mListener;
+
+    /**
+     * Reference to application context, used to access app resources. Holding it should not be a problem,
+     * since it needs to exist until the end of the AsyncTask although the caller Activity were finished
+     * before.
+     */
+    private final Context mAppContext;
+
+
+    public CopyAndUploadContentUrisTask(
+        OnCopyTmpFilesTaskListener listener,
+        Context context
+    ) {
+        mListener = new WeakReference<>(listener);
+        mAppContext = context.getApplicationContext();
+    }
+
+    /**
+     * @param params    Params to execute the task; see
+     *                  {@link #makeParamsToExecute(Account, Uri[], String[], ContentResolver)}
+     *                  for further details.
+     */
+    @Override
+    protected ResultCode doInBackground(Object[] params) {
+
+        ResultCode result = ResultCode.UNKNOWN_ERROR;
+
+        InputStream inputStream = null;
+        FileOutputStream outputStream = null;
+        String fullTempPath = null;
+        Uri currentUri = null;
+
+        try {
+            Account account = (Account) params[0];
+            Uri[] uris = (Uri[]) params[1];
+            String[] remotePaths = (String[]) params[2];
+            ContentResolver leakedContentResolver = (ContentResolver) params[3];
+
+            String currentRemotePath;
+
+            for (int i = 0; i < uris.length; i++) {
+                currentUri = uris[i];
+                currentRemotePath = remotePaths[i];
+
+                fullTempPath = FileStorageUtils.getTemporalPath(account.name) + currentRemotePath;
+                inputStream = leakedContentResolver.openInputStream(currentUri);
+                File cacheFile = new File(fullTempPath);
+                File tempDir = cacheFile.getParentFile();
+                if (!tempDir.exists()) {
+                    tempDir.mkdirs();
+                }
+                cacheFile.createNewFile();
+                outputStream = new FileOutputStream(fullTempPath);
+                byte[] buffer = new byte[4096];
+
+                int count;
+                while ((count = inputStream.read(buffer)) > 0) {
+                    outputStream.write(buffer, 0, count);
+                }
+
+                requestUpload(
+                    account,
+                    fullTempPath,
+                    currentRemotePath,
+                    leakedContentResolver.getType(currentUri)
+                );
+                fullTempPath = null;
+            }
+
+            result = ResultCode.OK;
+
+        } catch (ArrayIndexOutOfBoundsException e) {
+            Log_OC.e(TAG, "Wrong number of arguments received ", e);
+
+        } catch (ClassCastException e) {
+            Log_OC.e(TAG, "Wrong parameter received ", e);
+
+        } catch (FileNotFoundException e) {
+            Log_OC.e(TAG, "Could not find source file " + currentUri, e);
+            result = ResultCode.LOCAL_FILE_NOT_FOUND;
+
+        } catch (SecurityException e) {
+            Log_OC.e(TAG, "Not enough permissions to read source file " + currentUri, e);
+            result = ResultCode.FORBIDDEN;
+
+        } catch (Exception e) {
+            Log_OC.e(TAG, "Exception while copying " + currentUri + " to temporary file", e);
+            result =  ResultCode.LOCAL_STORAGE_NOT_COPIED;
+
+            // clean
+            if (fullTempPath != null) {
+                File f = new File(fullTempPath);
+                if (f.exists()) {
+                    if (!f.delete()) {
+                        Log_OC.e(TAG, "Could not delete temporary file " + fullTempPath);
+                    }
+                }
+            }
+
+        } finally {
+            if (inputStream != null) {
+                try {
+                    inputStream.close();
+                } catch (Exception e) {
+                    Log_OC.w(TAG, "Ignoring exception of inputStream closure");
+                }
+            }
+
+            if (outputStream != null) {
+                try {
+                    outputStream.close();
+                } catch (Exception e) {
+                    Log_OC.w(TAG, "Ignoring exception of outStream closure");
+                }
+            }
+        }
+
+        return result;
+    }
+
+    private void requestUpload(Account account, String localPath, String remotePath, String mimeType) {
+        FileUploader.UploadRequester requester = new FileUploader.UploadRequester();
+        requester.uploadNewFile(
+            mAppContext,
+            account,
+            localPath,
+            remotePath,
+            FileUploader.LOCAL_BEHAVIOUR_MOVE,  // the copy was already done, let's take advantage and move it
+                                                // into the OC folder so that appears as downloaded
+            mimeType,
+            false,      // do not create parent folder if not existent
+            UploadFileOperation.CREATED_BY_USER
+        );
+    }
+
+    @Override
+    protected void onPostExecute(ResultCode result) {
+        OnCopyTmpFilesTaskListener listener = mListener.get();
+        if (listener!= null) {
+            listener.onTmpFilesCopied(result);
+
+        } else {
+            Log_OC.i(TAG, "User left Uploader 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;
+                switch (result) {
+                    case LOCAL_FILE_NOT_FOUND:
+                        messageId = R.string.uploader_error_message_source_file_not_found;
+                        break;
+                    case LOCAL_STORAGE_NOT_COPIED:
+                        messageId = R.string.uploader_error_message_source_file_not_copied;
+                        break;
+                    case FORBIDDEN:
+                        messageId = R.string.uploader_error_message_read_permission_not_granted;
+                        break;
+                    default:
+                        messageId = R.string.common_error_unknown;
+                }
+                String message = String.format(
+                    mAppContext.getString(messageId),
+                    mAppContext.getString(R.string.app_name)
+                );
+                Toast.makeText(mAppContext, message, Toast.LENGTH_LONG).show();
+            }
+        }
+    }
+
+    /**
+     * Sets the object waiting for progress report via callbacks.
+     *
+     * @param listener      New object to report progress via callbacks
+     */
+    public void setListener(OnCopyTmpFilesTaskListener listener) {
+        mListener = new WeakReference<>(listener);
+    }
+
+    /**
+     * Interface to retrieve data from recognition task
+     */
+    public interface OnCopyTmpFilesTaskListener {
+        void onTmpFilesCopied(ResultCode result);
+    }
+}

+ 40 - 22
src/com/owncloud/android/ui/dialog/ConfirmationDialogFragment.java

@@ -27,14 +27,14 @@ import android.os.Bundle;
 import android.support.v4.app.DialogFragment;
 
 import com.owncloud.android.R;
-import com.owncloud.android.lib.common.utils.Log_OC;
 
 
 public class ConfirmationDialogFragment extends DialogFragment {
 
-    public final static String ARG_CONF_RESOURCE_ID = "resource_id";
-    public final static String ARG_CONF_ARGUMENTS = "string_array";
-    
+    public final static String ARG_MESSAGE_RESOURCE_ID = "resource_id";
+    public final static String ARG_MESSAGE_ARGUMENTS = "string_array";
+    private static final String ARG_TITLE_ID = "title_id";
+
     public final static String ARG_POSITIVE_BTN_RES = "positive_btn_res";
     public final static String ARG_NEUTRAL_BTN_RES = "neutral_btn_res";
     public final static String ARG_NEGATIVE_BTN_RES = "negative_btn_res";
@@ -46,18 +46,33 @@ public class ConfirmationDialogFragment extends DialogFragment {
     /**
      * Public factory method to create new ConfirmationDialogFragment instances.
      * 
-     * @param string_id         Resource id for a message to show in the dialog.
-     * @param arguments         Arguments to complete the message, if it's a format string.
-     * @param posBtn            Resource id for the text of the positive button.
-     * @param neuBtn            Resource id for the text of the neutral button.
-     * @param negBtn            Resource id for the text of the negative button.
+     * @param messageResId      Resource id for a message to show in the dialog.
+     * @param messageArguments  Arguments to complete the message, if it's a format string. May be null.
+     * @param titleResId        Resource id for a text to show in the title.
+     *                          0 for default alert title, -1 for no title.
+     * @param posBtn            Resource id for the text of the positive button. -1 for no positive button.
+     * @param neuBtn            Resource id for the text of the neutral button. -1 for no neutral button.
+     * @param negBtn            Resource id for the text of the negative button. -1 for no negative button.
      * @return                  Dialog ready to show.
      */
-    public static ConfirmationDialogFragment newInstance(int string_id, String[] arguments, int posBtn, int neuBtn, int negBtn) {
+    public static ConfirmationDialogFragment newInstance(
+        int messageResId,
+        String[] messageArguments,
+        int titleResId,
+        int posBtn,
+        int neuBtn,
+        int negBtn
+    ) {
+
+        if (messageResId == -1) {
+            throw new IllegalStateException("Calling confirmation dialog without message resource");
+        }
+
         ConfirmationDialogFragment frag = new ConfirmationDialogFragment();
         Bundle args = new Bundle();
-        args.putInt(ARG_CONF_RESOURCE_ID, string_id);
-        args.putStringArray(ARG_CONF_ARGUMENTS, arguments);
+        args.putInt(ARG_MESSAGE_RESOURCE_ID, messageResId);
+        args.putStringArray(ARG_MESSAGE_ARGUMENTS, messageArguments);
+        args.putInt(ARG_TITLE_ID, titleResId);
         args.putInt(ARG_POSITIVE_BTN_RES, posBtn);
         args.putInt(ARG_NEUTRAL_BTN_RES, neuBtn);
         args.putInt(ARG_NEGATIVE_BTN_RES, negBtn);
@@ -71,25 +86,28 @@ public class ConfirmationDialogFragment extends DialogFragment {
 
     @Override
     public Dialog onCreateDialog(Bundle savedInstanceState) {
-        Object[] confirmationTarget = getArguments().getStringArray(ARG_CONF_ARGUMENTS);
-        int resourceId = getArguments().getInt(ARG_CONF_RESOURCE_ID, -1);
+        Object[] messageArguments = getArguments().getStringArray(ARG_MESSAGE_ARGUMENTS);
+        int messageId = getArguments().getInt(ARG_MESSAGE_RESOURCE_ID, -1);
+        int titleId = getArguments().getInt(ARG_TITLE_ID, -1);
         int posBtn = getArguments().getInt(ARG_POSITIVE_BTN_RES, -1);
         int neuBtn = getArguments().getInt(ARG_NEUTRAL_BTN_RES, -1);
         int negBtn = getArguments().getInt(ARG_NEGATIVE_BTN_RES, -1);
         
-        if (confirmationTarget == null || resourceId == -1) {
-            Log_OC.wtf(getTag(), "Calling confirmation dialog without resource or arguments");
-            return null;
+        if (messageArguments == null) {
+            messageArguments = new String[]{};
         }
 
         AlertDialog.Builder builder = new AlertDialog.Builder(getActivity(), R.style.Theme_ownCloud_Dialog)
             .setIcon(R.drawable.ic_warning)
-            .setMessage(String.format(getString(resourceId), confirmationTarget))
-            .setTitle(android.R.string.dialog_alert_title);
-        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB) {
-            builder.setIconAttribute(android.R.attr.alertDialogIcon);
+            .setIconAttribute(android.R.attr.alertDialogIcon)
+            .setMessage(String.format(getString(messageId), messageArguments));
+
+        if (titleId == 0) {
+            builder.setTitle(android.R.string.dialog_alert_title);
+        } else if (titleId != -1) {
+            builder.setTitle(titleId);
         }
-        
+
         if (posBtn != -1)
             builder.setPositiveButton(posBtn,
                     new DialogInterface.OnClickListener() {

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

@@ -62,8 +62,8 @@ implements ConfirmationDialogFragmentListener {
             negBtn = R.string.confirmation_remove_local;
         }
         
-        args.putInt(ARG_CONF_RESOURCE_ID, messageStringId);
-        args.putStringArray(ARG_CONF_ARGUMENTS, new String[]{file.getFileName()});
+        args.putInt(ARG_MESSAGE_RESOURCE_ID, messageStringId);
+        args.putStringArray(ARG_MESSAGE_ARGUMENTS, new String[]{file.getFileName()});
         args.putInt(ARG_POSITIVE_BTN_RES, R.string.common_yes);
         args.putInt(ARG_NEUTRAL_BTN_RES, R.string.common_no);
         args.putInt(ARG_NEGATIVE_BTN_RES, negBtn);

+ 0 - 142
src/com/owncloud/android/utils/CopyTmpFileAsyncTask.java

@@ -1,142 +0,0 @@
-/**
- *   ownCloud Android client application
- *
- *   @author masensio
- *   Copyright (C) 2015 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.utils;
-
-import android.content.ContentResolver;
-import android.net.Uri;
-import android.os.AsyncTask;
-
-import com.owncloud.android.lib.common.utils.Log_OC;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.InputStream;
-import java.lang.ref.WeakReference;
-
-/**
- * AsyncTask to copy a file from a uri in a temporal file
- */
-public class CopyTmpFileAsyncTask  extends AsyncTask<Object, Void, String> {
-
-    private final String TAG = CopyTmpFileAsyncTask.class.getSimpleName();
-    private final WeakReference<OnCopyTmpFileTaskListener> mListener;
-    private int mIndex;
-
-    public int getIndex(){
-        return mIndex;
-    }
-
-    public CopyTmpFileAsyncTask(OnCopyTmpFileTaskListener listener) {
-        mListener = new WeakReference<OnCopyTmpFileTaskListener>(listener);
-    }
-
-    /**
-     * Params for execute:
-     * - Uri: uri of file
-     * - String: path for saving the file into the app
-     * - int: index of upload
-     * - String: accountName
-     * - ContentResolver: content resolver
-     */
-    @Override
-    protected String doInBackground(Object[] params) {
-        String result = null;
-
-        if (params != null && params.length == 5) {
-            Uri uri = (Uri) params[0];
-            String filePath = (String) params[1];
-            mIndex = ((Integer) params[2]).intValue();
-            String accountName = (String) params[3];
-            ContentResolver contentResolver = (ContentResolver) params[4];
-
-            String fullTempPath = FileStorageUtils.getTemporalPath(accountName) + filePath;
-            InputStream inputStream = null;
-            FileOutputStream outputStream = null;
-
-            try {
-                inputStream = contentResolver.openInputStream(uri);
-                File cacheFile = new File(fullTempPath);
-                File tempDir = cacheFile.getParentFile();
-                if (!tempDir.exists()) {
-                    tempDir.mkdirs();
-                }
-                cacheFile.createNewFile();
-                outputStream = new FileOutputStream(fullTempPath);
-                byte[] buffer = new byte[4096];
-
-                int count = 0;
-
-                while ((count = inputStream.read(buffer)) > 0) {
-                    outputStream.write(buffer, 0, count);
-                }
-
-                outputStream.close();
-                inputStream.close();
-
-                result = fullTempPath;
-            } catch (Exception e) {
-                 Log_OC.e(TAG, "Exception ", e);
-                if (inputStream != null) {
-                    try {
-                        inputStream.close();
-                    } catch (Exception e1) {
-                        Log_OC.e(TAG, "Input Stream Exception ", e1);
-                    }
-                }
-
-                if (outputStream != null) {
-                    try {
-                        outputStream.close();
-                    } catch (Exception e1) {
-                        Log_OC.e(TAG, "Output Stream Exception ", e1);
-                    }
-                }
-
-                if (fullTempPath != null) {
-                    File f = new File(fullTempPath);
-                    f.delete();
-                }
-                result =  null;
-            }
-        } else {
-             throw new IllegalArgumentException("Error in parameters number");
-        }
-
-        return result;
-    }
-
-    @Override
-    protected void onPostExecute(String result) {
-
-        OnCopyTmpFileTaskListener listener = mListener.get();
-        if (listener!= null)
-        {
-            listener.onTmpFileCopied(result, mIndex);
-        }
-    }
-
-    /*
-     * Interface to retrieve data from recognition task
-     */
-    public interface OnCopyTmpFileTaskListener{
-
-        void onTmpFileCopied(String result, int index);
-    }
-}

+ 93 - 4
src/com/owncloud/android/utils/UriUtils.java

@@ -20,6 +20,7 @@
 package com.owncloud.android.utils;
 
 import android.annotation.TargetApi;
+import android.content.ContentResolver;
 import android.content.ContentUris;
 import android.content.Context;
 import android.database.Cursor;
@@ -28,13 +29,10 @@ import android.os.Build;
 import android.os.Environment;
 import android.provider.DocumentsContract;
 import android.provider.MediaStore;
-import android.provider.OpenableColumns;
+import android.webkit.MimeTypeMap;
 
-import com.owncloud.android.MainApp;
 import com.owncloud.android.lib.common.utils.Log_OC;
 
-import java.io.File;
-
 
 /**
  * A helper class for some Uri operations.
@@ -190,4 +188,95 @@ public class UriUtils {
         return null;
     }
 
+
+
+    public static String getDisplayNameForUri(Uri uri, Context context) {
+
+        if (uri == null || context == null) {
+            throw new IllegalArgumentException("Received NULL!");
+        }
+
+        String displayName = null;
+
+        if (!ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) {
+            displayName = uri.getLastPathSegment();     // ready to return
+
+        } else {
+            // content: URI
+
+            displayName = getDisplayNameFromContentResolver(uri, context);
+
+            try {
+                if (displayName == null) {
+                    // last chance to have a name
+                    displayName = uri.getLastPathSegment().replaceAll("\\s", "");
+                }
+
+                // Add best possible extension
+                int index = displayName.lastIndexOf(".");
+                if (index == -1 || MimeTypeMap.getSingleton().
+                    getMimeTypeFromExtension(displayName.substring(index + 1)) == null) {
+                    String mimeType = context.getContentResolver().getType(uri);
+                    String extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeType);
+                    if (extension != null) {
+                        displayName += "." + extension;
+                    }
+                }
+
+            } catch (Exception e) {
+                Log_OC.e(TAG, "No way to get a display name for " + uri.toString());
+            }
+        }
+
+        // Replace path separator characters to avoid inconsistent paths
+        return displayName.replaceAll("/", "-");
+    }
+
+
+    private static String getDisplayNameFromContentResolver(Uri uri, Context context) {
+        String displayName = null;
+        String mimeType = context.getContentResolver().getType(uri);
+        if (mimeType != null) {
+            String displayNameColumn;
+            if (mimeType.toLowerCase().startsWith("image/")) {
+                displayNameColumn = MediaStore.Images.ImageColumns.DISPLAY_NAME;
+
+            } else if (mimeType.toLowerCase().startsWith("video/")) {
+                displayNameColumn = MediaStore.Video.VideoColumns.DISPLAY_NAME;
+
+            } else if (mimeType.toLowerCase().startsWith("audio/")) {
+                displayNameColumn = MediaStore.Audio.AudioColumns.DISPLAY_NAME;
+
+            } else {
+                displayNameColumn = MediaStore.Files.FileColumns.DISPLAY_NAME;
+            }
+
+            Cursor cursor = null;
+            try {
+                cursor = context.getContentResolver().query(
+                    uri,
+                    new String[]{displayNameColumn},
+                    null,
+                    null,
+                    null
+                );
+                if (cursor != null) {
+                    cursor.moveToFirst();
+                    displayName = cursor.getString(cursor.getColumnIndex(displayNameColumn));
+                }
+
+            } catch (Exception e) {
+                Log_OC.e(TAG, "Could not retrieve display name for " + uri.toString());
+                // nothing else, displayName keeps null
+
+            } finally {
+                if (cursor != null) {
+                    cursor.close();
+                }
+            }
+        }
+        return displayName;
+    }
+
+
 }